Следует помнить, что номер недели datetime.datetime
(который представлен %Y
в strptime
и strftime
) имеет нулевой индекс. Принимая во внимание, что из вашего примера данных вы используете версию с 1 индексом.
from datetime import datetime, timedelta
import pandas as pd
my_date = "2020 23"
start = datetime.strptime(my_date + " 5", "%Y %W %w").date() - timedelta(weeks=1)
dates = [start + timedelta(days=i) for i in range(14)]
date_strings = [d.strftime("%d-%m-%Y") for d in dates]
date_codes = ["{}{}_{}".format(*(d - timedelta(days=4)).isocalendar()) for d in dates]
dates_df = pd.DataFrame({"Year_Week": date_codes, "Date": date_strings})
В некоторых из них происходит довольно много, поэтому давайте разберемся:
Требуется импорт классы:
from datetime import datetime, timedelta
import pandas as pd
Настроить массив требуемых дат:
Во-первых, мы анализируем входную строку и можем сразу извлечь только компонент даты в той же строке, используя .date()
:
datetime.strptime(my_date + " 5", "%Y %W %w").date()
Как я упоминал выше, номер недели datetime
индексируется 0, поэтому, когда мы делаем strptime
с %Y
на 23
, мы получаем 24-я неделя. Это означает, что затем нам нужно вернуться на неделю, чтобы получить день, который мы действительно хотели:
start = datetime.strptime(my_date + " 5", "%Y %W %w").date() - timedelta(weeks=1)
Наконец, мы используем понимание списка, как и вы:
dates = [start + timedelta(days=i) for i in range(14)]
Создайте наши столбцы DataFrame:
strftime()
- это обратное strptime()
, а формат вашего Date
столбца - dd-mm-yyyy
, что соответствует формату строка, используемая здесь:
date_strings = [d.strftime("%d-%m-%Y") for d in dates]
Следующая строка имеет наибольшее значение сразу:
Во-первых, обратите внимание, что date
и Объекты datetime
имеют метод isocalendar()
, который возвращает кортеж (год ISO, неделя ISO, день ISO). Недели ISO проходят с понедельника = 1 до воскресенья = 7, а нумерация недель начинается с 1, а не с 0.
Таким образом, ваши «недели» точно совпадают, но сначала сдвинуты на 4 дня. Пятница = 1. Таким образом, каждая из ваших дат имеет соответствующий номер дня недели / дня по ISO 4 дня назад . Поэтому мы сдвигаем вашу дату назад на 4 дня, а затем извлекаем номера года / недели / дня: d - timedelta(days=4)).isocalendar()
С помощью "{}{}_{}".format()
мы настраиваем шаблон для добавления года / значения недели / дня. Каждая пара фигурных скобок {}
указывает, где каждое значение, переданное в format()
, должно быть вставлено в шаблон строки. Например,
"{}{}_{}".format(2020, 23, 4)
даст нам "202023_4"
, код на 8 июня 2020 года.
Использование *
на результат нашего вызова функции .isocalendar()
распаковывает кортеж для передачи его значений по отдельности в format()
Собирая все вместе как понимание списка, снова используя список дат мы создали ранее:
date_codes = ["{}{}_{}".format(*(d - timedelta(days=4)).isocalendar()) for d in dates]
Построение DataFrame
Мы передаем данные в виде словаря в формате {"Column Name": column_values_list}
:
dates_df = pd.DataFrame({"Year_Week": date_codes, "Date": date_strings})
Мы могли бы обернуть все это как функцию, что также означало бы, что нам не нужно использовать строку в качестве отправной точки - мы можем просто передать правильные числа напрямую:
from datetime import date, timedelta
import pandas as pd
def create_table(year, week, n=1):
start = date.fromisocalendar(year, week, 5)
dates = [start + timedelta(days=i) for i in range(n * 7)]
date_strings = [d.strftime("%d-%m-%Y") for d in dates]
date_codes = ["{}{}_{}".format(*(d - timedelta(days=4)).isocalendar()) for d in dates]
return pd.DataFrame({"Year_Week": date_codes, "Date": date_strings})
table = create_table(2020, 23, 2)
print(table)
Вывод:
Year_Week Date
0 202023_1 05-06-2020
1 202023_2 06-06-2020
2 202023_3 07-06-2020
3 202023_4 08-06-2020
4 202023_5 09-06-2020
5 202023_6 10-06-2020
6 202023_7 11-06-2020
7 202024_1 12-06-2020
8 202024_2 13-06-2020
9 202024_3 14-06-2020
10 202024_4 15-06-2020
11 202024_5 16-06-2020
12 202024_6 17-06-2020
13 202024_7 18-06-2020
Обратите внимание, что у нас есть необязательный третий параметр n
, чтобы сказать, для скольких недель мы хотим создать таблицу (по умолчанию 1). Кроме того, поскольку мы напрямую передаем год и номер недели, мы можем использовать встроенный метод date.fromisocalendar()
, который является обратным методу .isocalendar()
. Это берет год, неделю и день и напрямую возвращает соответствующую дату.
Обновление: адаптации для совместимости с Python 3.6 и гибкий ввод
Получение даты начала в Python 3.6 без использования .fromisocalendar()
date.fromisocalendar()
было введено только в Python 3.7, поэтому, если вы используете более раннюю версию Python, вам придется использовать более сложную технику записи строки для последующего анализа с помощью strptime()
.
Однако, если вы используя Python 3.6, были добавлены некоторые новые директивы форматирования для синтаксического анализа недельных дат ISO, которые делают это немного проще, и мы можем использовать подход из этого ответа SO :
def date_from_isoweek(year, week, day):
return datetime.strptime(f"{year:04d} {week:02d} {day:d}", "%G %V %u").date()
Мы Используем f-строку для создания строки даты для последующего анализа в виде datetime
, из которого мы извлекаем компонент date
. Например, :02d
после week
внутри фигурных скобок {}
гарантирует, что он правильно отформатирован как 2-di git десятичное число с заполнением слева 0
(что нам нужно, если номер нашей недели находится между 1-9) .
Разрешить ввод в качестве начальной и конечной даты
Это довольно просто, так как есть встроенная функция pandas с именем date_range()
, которая принимает параметры start
и end
, которые могут быть объектами date
/ datetime
или строками. Он предназначен для создания индекса datetime, но его очень легко превратить в список дат.
dates = pd.date_range(start, end).date.tolist()
Объединение
Если мы проведем рефакторинг нашего кода, чтобы отделить часть, которая создает список дат, который мы хотим в нашей таблице, и часть, которая затем форматирует их для создания данных для наших столбцов и помещает их в наш фрейм данных, мы получаем следующее:
def create_table_from_dates(dates):
date_strings = [d.strftime("%d-%m-%Y") for d in dates]
date_codes = [(d - timedelta(days=4)).strftime("%G%V_%u") for d in dates]
return pd.DataFrame({"Year_Week": date_codes, "Date": date_strings})
def create_table_between_dates(start, end):
dates = pd.date_range(start, end).date.tolist()
return create_table_from_dates(dates)
def create_table_by_weeks(year, week, n=1):
friday_as_isoweek_string = f"{year:04d} {week:02d} 5"
start = datetime.strptime(friday_as_isoweek_string, "%G %V %u").date()
dates = [start + timedelta(days=i) for i in range(n * 7)]
return create_table_from_dates(dates)
table_by_weeks = create_table_by_weeks(2020, 23, 2)
table_from_range = create_table_between_dates("2020-06-05", "2020-06-28")
create_table_by_weeks()
имеет та же подпись, что и наша функция create_table()
из исходного ответа. create_table_between_dates()
принимает дату start
и end
либо как объекты даты, либо как строки. Обе эти функции создают список дат для таблицы, а затем передают их функции create_table_from_dates()
(вверху) для создания DataFrame.
Изменение формата выходных строк
Часть кода, которая определяет, как выглядит столбец Year_week
, - это эта строка в функции create_table_from_dates()
:
date_codes = [(d - timedelta(days=4)).strftime("%G%V_%u") for d in dates]
, а именно строка "%G%V_%u"
внутри вызова метода strftime()
. Вы можете настроить это, используя коды формата, указанные в таблице здесь: https://docs.python.org/3/library/datetime.html#strftime -and-strptime-format-codes
Помните: Мы получаем наши коды путем небольшого жульничества: поскольку ваши «недели» - это всего лишь календарные недели ISO, но смещенные на пятницу, мы просто «крадем» номер недели и дня ISO из четырех дней ранее. Если вы просто играете с порядком или дополнительными символами, это нормально: изменение "%G%V_%u"
на "%u_%G%V"
изменит 202023_1
на 1_202023
. Но если вы хотите включить такие вещи, как фактическая дата или день недели, вам нужно будет убедиться, что вы получили эти компоненты с истинной даты (а не с даты 4 дня назад).
date_codes = [
(d - timedelta(days=4)).strftime("%G%V_%u") + d.strftime(" %a %d %b")
for d in dates
]
даст нам даты вроде 202023_1 Fri 05 Jun
Если это только год / неделя / день, с которым вы хотите работать, мы можем извлечь эту строку формата как переменную fmt
, и передать его в create_table_from_dates()
из двух других функций и сделать его аргументом ключевого слова (со значением по умолчанию) для обеих из них:
def create_table_from_dates(dates, fmt):
date_strings = [d.strftime("%d-%m-%Y") for d in dates]
date_codes = [(d - timedelta(days=4)).strftime(fmt) for d in dates]
return pd.DataFrame({"Year_Week": date_codes, "Date": date_strings})
def create_table_between_dates(start, end, fmt="%G%V_%u"):
dates = pd.date_range(start, end).date.tolist()
return create_table_from_dates(dates, fmt)
def create_table_by_weeks(year, week, n=1, fmt="%G%V_%u"):
friday_as_isoweek_string = f"{year:04d} {week:02d} 5"
start = datetime.strptime(friday_as_isoweek_string, "%G %V %u").date()
dates = [start + timedelta(days=i) for i in range(n * 7)]
return create_table_from_dates(dates, fmt)
table = create_table_by_weeks(2020, 23, 2, fmt="%u_%G%V")
print(table)
Будет выдан следующий результат:
Year_Week Date
0 1_202023 05-06-2020
1 2_202023 06-06-2020
2 3_202023 07-06-2020
3 4_202023 08-06-2020
4 5_202023 09-06-2020
5 6_202023 10-06-2020
6 7_202023 11-06-2020
7 1_202024 12-06-2020
8 2_202024 13-06-2020
9 3_202024 14-06-2020
10 4_202024 15-06-2020
11 5_202024 16-06-2020
12 6_202024 17-06-2020
13 7_202024 18-06-2020