Python - Работа с датами - PullRequest
       4

Python - Работа с датами

0 голосов
/ 21 июня 2020

У меня небольшое требование, чтобы получить отображение даты. У меня есть следующий код:

Этот код принимает номер года и недели и получает «пятницу» этой недели в качестве начальной даты.

from datetime import datetime
myDate = "2020 23"
df = datetime.strptime(myDate + ' 5', "%Y %W %w")
df = datetime.date(df)

Приведенный ниже код принимает начальную date сверху и получает даты на следующие 7 дней.

import datetime
start = df
# build a simple range
dates = [start + datetime.timedelta(days=d) for d in range(7)]
dates = [str(d) for d in dates]
dates_df = pd.DataFrame(dates,columns = ['dates'])

Как это можно преобразовать в следующую таблицу с соглашениями об именах?

Таблица

введите описание изображения здесь

Любая помощь приветствуется!

Спасибо!

1 Ответ

1 голос
/ 21 июня 2020

Следует помнить, что номер недели 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...