Еженедельный трекер дозы с указанием времени и средней дозы

Функция:

Выводит итоговые суточные и недельные дозы со средним значением времени между дозами и количеством дозы.

Код:


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 ответ
1

Правильно ли я использую классы?

В общем, да, хотя всегда есть возможности для улучшения.

Это ООП?

Да!

Предложите улучшения

time_dose имеет в качестве ключевого типа float, что (вообще говоря) не является хорошим представлением времени в Python. datetime.time следует использовать вместо этого. Поскольку вы используете эти значения через среднее значение, возможно, будет оправдано использование числа с плавающей запятой, но

  1. поясняя, что это время используется как время с NewType псевдоним типа и
  2. с использованием стандартного формата временных меток эпохи 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

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *