Группировка по неделям для каждого года в django
Я нашел этот комментарий от @HenryHeikkinen очень полезным Как группировать по неделям в postgresql
Так что я исправил свой код. Но все равно не все в порядке. Я обнаружил несколько ситуаций, когда он работает неправильно. Когда год меняется в течение первой недели.
В моей модели данных хранится некоторое значение для каждого дня в году. Я хочу сгруппировать данные по неделям и сравнить несколько лет.
2018 год - это интересно 1.1.2018 - понедельник, поэтому первая неделя 2018 года начинается в этот день. последняя неделя 2018 года - 52 и начинается 24.12.2018
31.12.2018 - понедельник следующей недели, но это первая неделя 2019 года, и это нормально. Но при группировке по неделям результат для этой недели (неделя 1 2019 года) имеет дату недели = 31.12.2018, поэтому извлечение информации о годе из этого результата дает 2018 год вместо 2019. и таким образом получается дубликат для первой недели 2018 года.
Год 2019 имеет эту проблему в начале и в конце.
- Неделя 1 2019 года начинается 31.12.2018
- Неделя 1 2020 года начинается 30.12.2019
вот мой код django, который возвращает данные за неделю
self.dataday_set.annotate(ww=TruncWeek('date')) \
.values('ww') \
.annotate(consumption=Sum('consumption'), amount=Max('amount'), w=ExtractWeek('ww'), r=ExtractYear('ww')) \
.order_by('-ww')
вот раздел вывода между 2017 и 2018 годами, который в порядке
{'ww': datetime.date(2018, 1, 8), 'consumption': Decimal('42.39700'), 'amount': Decimal('12878.230'), 'w': 2, 'r': 2018}
{'ww': datetime.date(2018, 1, 1), 'consumption': Decimal('56.60000'), 'amount': Decimal('12835.833'), 'w': 1, 'r': 2018}
{'ww': datetime.date(2017, 12, 25), 'consumption': Decimal('45.95800'), 'amount': Decimal('12779.233'), 'w': 52, 'r': 2017}
{'ww': datetime.date(2017, 12, 18), 'consumption': Decimal('43.71600'), 'amount': Decimal('12733.275'), 'w': 51, 'r': 2017}
раздел между 2018 и 2019 годами, и, как вы можете видеть, неделя 1 2019 года указана как неделя 2018 года
{'ww': datetime.date(2019, 1, 14), 'consumption': Decimal('40.03400'), 'amount': Decimal('14911.079'), 'w': 3, 'r': 2019}
{'ww': datetime.date(2019, 1, 7), 'consumption': Decimal('47.14400'), 'amount': Decimal('14871.045'), 'w': 2, 'r': 2019}
{'ww': datetime.date(2018, 12, 31), 'consumption': Decimal('52.35000'), 'amount': Decimal('14823.901'), 'w': 1, 'r': 2018}
{'ww': datetime.date(2018, 12, 24), 'consumption': Decimal('48.69800'), 'amount': Decimal('14771.551'), 'w': 52, 'r': 2018}
{'ww': datetime.date(2018, 12, 17), 'consumption': Decimal('45.07700'), 'amount': Decimal('14722.853'), 'w': 51, 'r': 2018}
{'ww': datetime.date(2018, 12, 10), 'consumption': Decimal('44.17100'), 'amount': Decimal('14677.776'), 'w': 50, 'r': 2018}
и раздел между 2019 и 2020, та же проблема
{'ww': datetime.date(2020, 1, 13), 'consumption': Decimal('39.31200'), 'amount': Decimal('16976.174'), 'w': 3, 'r': 2020}
{'ww': datetime.date(2020, 1, 6), 'consumption': Decimal('50.84500'), 'amount': Decimal('16936.862'), 'w': 2, 'r': 2020}
{'ww': datetime.date(2019, 12, 30), 'consumption': Decimal('51.67200'), 'amount': Decimal('16886.017'), 'w': 1, 'r': 2019}
{'ww': datetime.date(2019, 12, 23), 'consumption': Decimal('49.71200'), 'amount': Decimal('16834.345'), 'w': 52, 'r': 2019}
{'ww': datetime.date(2019, 12, 16), 'consumption': Decimal('43.23600'), 'amount': Decimal('16784.633'), 'w': 51, 'r': 2019}
{'ww': datetime.date(2019, 12, 9), 'consumption': Decimal('41.13100'), 'amount': Decimal('16741.397'), 'w': 50, 'r': 2019}
Я передаю этот набор запросов в pandas dataframe и создаю таблицу pivot из полей r w и consumption.
Спасибо за любой совет, как я могу решить это в django вместо того, чтобы создавать какой-то собственный код. Или может быть есть хорошее решение с помощью pandas, используя только ww и поле потребления для создания pivot таблицы, где у меня есть годы в строке и недели в столбцах
Этот вопрос или рассуждения описаны здесь: https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
Номер недели года по стандарту ISO 8601. По определению, недели ISO начинаются по понедельникам, и первая неделя года содержит 4 января этого года. Другими словами, первый четверг года находится на первой неделе этого года.
В системе нумерации недель ISO возможно, что даты начала января входят в 52-ю неделю года. могут быть частью 52-й или 53-й недели предыдущего года, и для дат конца декабря - часть первой недели следующего года. Например, 2005-01-01 является частью 53-й недели 2004 года, а 2006-01-01 является частью 52-й недели 2005 года, а 2012-12-31 является часть первой недели 2013 года. Рекомендуется использовать поле isoyear вместе с полем week для получения последовательных результатов.
Я предлагаю вам рассчитать неделю следующим образом:
self.dataday_set\
.values('date')\
.annotate(
consumption=Sum('consumption'),
amount=Max('amount'),
w=Greatest(Round(Extract('date', 'doy') / 7), 1),
r=Extract('date', 'year')
)\
.order_by('-date')
Round() используется для округления до ближайшего целого числа (PostgreSQL округляет 0.5 до 1), Greatest() используется для корректировки на первую неделю года.
В целом, это все еще будет проблемой в определенных ситуациях. Например, 2022-01-03 будет неделей 1, а вы ожидаете неделю 2. Неделя 1 будет правильной, если учесть, что неделя начинается в понедельник. Но 2022-01-17 будет рассчитан неправильно.
Вы должны выбрать между неделей ISO 8601 или этим.
Я бы использовал ISO 8601, как предлагает PostgreSQL, и изменил бы код на:
self.dataday_set\
.values('date')\
.annotate(
consumption=Sum('consumption'),
amount=Max('amount'),
w=Extract('date', 'week'),
r=Extract('date', 'isoyear')
)\
.order_by('-date')
Я нашел решение и окончательный код django выглядит так
self.dataday_set.annotate(ww=TruncWeek('date')) \
.values('ww') \
.annotate(consumption=Sum('consumption'), amount=Max('amount'), w=ExtractWeek('ww'), r=ExtractIsoYear('ww')) \
.order_by('-ww')