Функция:
Выводит итоговые суточные и недельные дозы со средним значением времени между дозами и количеством дозы.
Код:
from decimal import Decimal
from dataclasses import dataclass
from typing import List, Dict, Iterable, Collection
UNIT = 'u'
SUBSTANCE = 'sub'
@dataclass
class DayDoseMean:
day: str
time_dose: Dict[float, float]
@property
def doses(self) -> List[Decimal]:
return [
round(
Decimal(i), 1
) for i in self.time_dose.values()
]
@property
def times(self) -> List[Decimal]:
return [
round(
Decimal(i), 2
) for i in self.time_dose.keys()
]
@property
def daily_dose(self) -> Decimal:
return sum(i for i in self.doses)
@property
def mean(self) -> Decimal:
return self.daily_dose / len(self.doses)
@property
def diff(self) -> Iterable[Decimal]:
return (
abs(
self.times[i] - self.times[i+1]
) for i in range(len(self.times) - 1)
)
@property
def time_mean(self) -> Decimal:
return sum(self.diff) / len(self.times)
@property
def prnt(self):
return (
f'{self.day}{":":4}'
f'{self.daily_dose}{UNIT:4}'
f'{self.mean}{self.time_mean:7}'
)
@dataclass
class WeekDoseMean:
week_dose_mean: Collection[DayDoseMean]
@property
def weekly_dose(self) -> Decimal:
return sum(
i.daily_dose
for i in self.week_dose_mean
)
@property
def weekly_mean(self) -> Decimal:
return round(
sum(
i.mean
for i in self.week_dose_mean
) / 7, 1
)
@property
def weekly_time_mean(self) -> Decimal:
return round(
sum(
i.time_mean
for i in self.week_dose_mean
) / 7, 2
)
@property
def echo(self):
print(
f'n--------------------------n'
f'{"Day":7}{SUBSTANCE:7}'
f'{"dM":6}tMn'
f'--------------------------'
)
print(
'n'
.join(
i.prnt
for i in self.week_dose_mean
)
)
print(
f'--------------------------n'
f'Weekly {"dM":4} -> mean: '
f'{self.weekly_mean}n'
f'Weekly {"tM":4} -> mean: '
f'{self.weekly_time_mean}n'
f'--------------------------n'
f'Weekly dose -> {SUBSTANCE}:'
f'{self.weekly_dose}{UNIT}n'
f'--------------------------'
)
def main():
week_date = WeekDoseMean(
week_dose_mean=(
DayDoseMean(
day='Mon',
time_dose={
12: 1,
13: 1
}
),
DayDoseMean(
day='Tue',
time_dose={
12: 1,
13: 1
}
),
DayDoseMean(
day='Wed',
time_dose={
12: 1,
13: 1
}
),
DayDoseMean(
day='Thu',
time_dose={
12: 1,
13: 1
}
),
DayDoseMean(
day='Fri',
time_dose={
12: 1,
13: 1
}
),
DayDoseMean(
day='Sat',
time_dose={
12: 1,
13: 1
}
),
DayDoseMean(
day='Sun',
time_dose={
12: 2,
14: 2
}
)
)
)
week_date.echo
if __name__ == '__main__':
main()
Выход:
--------------------------
Day phen dM tM
--------------------------
Mon: 2.0u 1.0 0.50
Tue: 2.0u 1.0 0.50
Wed: 2.0u 1.0 0.50
Thu: 2.0u 1.0 0.50
Fri: 2.0u 1.0 0.50
Sat: 2.0u 1.0 0.50
Sun: 4.0u 2.0 1.00
--------------------------
Weekly dM -> mean: 1.1
Weekly tM -> mean: 0.57
--------------------------
Weekly dose -> sub:16.0u
--------------------------
Помощь:
Я еще не знаю, понимаю ли я классы и ООП.
- Правильно ли я использую классы?
- Это ООП?
- Предложите улучшения
Задний план:
Я написал этот сценарий много лет назад, узнав много нового о классах и структурах данных от одного очень любезного члена.
Я пытался изучить ООП и классы.
1 ответ
Правильно ли я использую классы?
В общем, да, хотя всегда есть возможности для улучшения.
Это ООП?
Да!
Предложите улучшения
time_dose
имеет в качестве ключевого типа float
, что (вообще говоря) не является хорошим представлением времени в Python. datetime.time
следует использовать вместо этого. Поскольку вы используете эти значения через среднее значение, возможно, будет оправдано использование числа с плавающей запятой, но
- поясняя, что это время используется как время с
NewType
псевдоним типа и - с использованием стандартного формата временных меток эпохи 1970-х «Unix», а не того, что кажется дробными часами после полуночи.
DayDoseMean
это не лучшее имя, поскольку сам класс не представляет собой средство, а представляет собой только один из его членов; так что, возможно, назовите это DayDoses
. Аналогично с WeekDoses
.
Не сокращайте print
к prnt
.
Это немного продвинуто, но в CPython есть такая функция:
@property
def diff(self) -> Iterable[Decimal]:
return (
abs(
self.times[i] - self.times[i+1]
) for i in range(len(self.times) - 1)
)
в байт-коде создаст вторую скрытую функцию генератора, тогда как for/yield
не буду. Я только недавно узнал об этом, и я рекомендую вам использовать инструмент разборки, чтобы увидеть разницу.
{":":4}
и {UNIT:4}
странные. Вы помещаете разделитель в поле фиксированной ширины. Вероятно, это не то, что вам нужно. Вместо этого положите свой self.day
, daily_dose
и т.д. в полях фиксированной ширины, поскольку это строки, длина которых может варьироваться.
sum(i for i in self.doses)
может просто быть sum(self.doses)
.
Ты рассыпаешься round
вызовы по всему вашему аналитическому коду. Это не кажется хорошей идеей. Если это делается для целей форматирования, не делайте этого там, где он есть сейчас, а просто добавьте спецификатор фиксированной точности в свои интерполированные строки, например
f'{self.weekly_mean:.1f}n'
DayDoseMean.prnt
и WeekDoseMean.echo
оба свойства; первый правильно возвращает значение, а второй — нет. Чтобы исправить второе, либо:
- Сохраните его как свойство, но верните строку; или
- Удалите декоратор свойств и представьте его как обычный метод.
Вместо того, чтобы передавать дни недели в виде строк, используйте целые числа. Когда придет время печатать, используйте https://docs.python.org/3/library/calendar.html#calendar.day_name .
Панды
Для чего-то совсем другого: Pandas лучше подходит для такого рода обработки и анализа данных. Преимущества в том, что он немного более краток, вероятно, быстрее работает для больших наборов данных и более выразителен с точки зрения семантики группировки.
- Рассматривайте даты как первоклассные, а названия дней недели как второсортные.
- Ты уверен что
/ len(self.times)
что ты хочешь делать? Данныйn
раз в день естьn-1
дифференциальные значения. - Что вы хотите, чтобы свидания длились более одной недели? Вы все еще хотите сгруппировать по будням или просто хотите сгруппировать по дням? Я показал последнее, но данные вашего примера не проясняют это. В ваших данных выборки использование словаря названий дней недели для доз не особенно репрезентативно для реальности. Что, если ваши данные охватывают восемь дней? Вероятно, это должна быть просто последовательность дат.
- Не округлять нигде, кроме форматирования вывода
Предложенный
import numpy as np
from datetime import datetime
import pandas as pd
SUBSTANCE = 'phen'
def group_by_day(doses: pd.DataFrame) -> pd.DataFrame:
# Fractional hours after midnight
doses['hour'] = (
doses.index - doses.index.date.astype('datetime64[ns]')
) / np.timedelta64(1, 'h')
# Get dose sum, dose mean, and time-differential grouped by day (not weekday)
by_day = (
doses.groupby(by=doses.index.date)
.agg({
SUBSTANCE: ('sum', 'mean'),
'hour': lambda hour: hour.diff().mean(),
})
)
by_day.columns = (SUBSTANCE, 'dM', 'tM')
by_day.insert(
loc=0, column='weekday',
value=by_day.index.astype('datetime64[ns]').strftime('%a'),
)
return by_day
def summarize(by_day: pd.DataFrame) -> None:
print(
f'nWeekly dM mean: {by_day.dM.mean():.1f}'
f'nWeekly tM mean: {by_day.tM.mean():.2f}'
f'nWeekly dose: {SUBSTANCE}: {by_day[SUBSTANCE].sum():.1f}'
)
def main():
# All data, entered flat (not grouped by weekday), plain datetimes
times = (
datetime(2021, 8, 9, 12, 0), datetime(2021, 8, 9, 13, 0),
datetime(2021, 8, 10, 12, 0), datetime(2021, 8, 10, 13, 0),
datetime(2021, 8, 11, 12, 0), datetime(2021, 8, 11, 13, 0),
datetime(2021, 8, 12, 12, 0), datetime(2021, 8, 12, 13, 0),
datetime(2021, 8, 13, 12, 0), datetime(2021, 8, 13, 13, 0),
datetime(2021, 8, 14, 12, 0), datetime(2021, 8, 14, 13, 0),
datetime(2021, 8, 15, 12, 0), datetime(2021, 8, 15, 14, 0),
)
doses = pd.DataFrame(
(
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 2.0, 2.0,
),
columns=(SUBSTANCE,), index=times,
)
by_day = group_by_day(doses)
print(by_day)
summarize(by_day)
if __name__ == '__main__':
main()
Выход
weekday phen dM tM
2021-08-09 Mon 2.0 1.0 1.0
2021-08-10 Tue 2.0 1.0 1.0
2021-08-11 Wed 2.0 1.0 1.0
2021-08-12 Thu 2.0 1.0 1.0
2021-08-13 Fri 2.0 1.0 1.0
2021-08-14 Sat 2.0 1.0 1.0
2021-08-15 Sun 4.0 2.0 2.0
Weekly dM mean: 1.1
Weekly tM mean: 1.14
Weekly dose: phen: 16.0