#python #pandas #datetime #pytz #datetimeoffset
#python #панды #дата и время #pytz #datetimeoffset
Вопрос:
У меня есть два файла временных рядов, которые должны быть в CET / CEST. Плохой из них неправильно записывает значения. Для хорошего csv смотрите Здесь:
#test_good.csv
local_time,value
...
2017-03-26 00:00,2016
2017-03-26 01:00,2017
2017-03-26 03:00,2018
2017-03-26 04:00,2019
...
2017-10-29 01:00,7224
2017-10-29 02:00,7225
2017-10-29 02:00,7226
2017-10-29 03:00,7227
...
… все работает нормально, используя:
df['utc_time'] = pd.to_datetime(df[local_time_column])
.dt.tz_localize('CET', ambiguous="infer")
.dt.tz_convert('UTC').dt.strftime('%Y-%m-%d %H:%M:%S')
При преобразовании test_bad.csv в UTC я получаю неоднозначную ошибку Timeerror, поскольку 2 часа в октябре отсутствуют.
# test_bad.csv
local_time,value
...
2017-03-26 00:00,2016
2017-03-26 01:00,2017 # everything is as it should be
2017-03-26 03:00,2018
2017-03-26 04:00,2019
...
2017-10-29 01:00,7223
2017-10-29 02:00,7224 # the value of 2 am should actually be repeated PLUS 3 am is missing
2017-10-29 04:00,7226
2017-10-29 05:00,7227
...
Кто-нибудь знает элегантный способ, как все-таки преобразовать файл временных рядов в UTC и добавить столбцы NaN для недостающих дат в новом индексе? Спасибо за вашу помощь.
Комментарии:
1. чтобы правильно понять, во втором примере («плохие» данные) отсутствуют записи, и вы хотите заполнить их NaN? Кроме того, является ли частота постоянной (почасовой)?
2. @MrFuppes, да, я имею дело с почасовыми данными. В лучшем случае я хотел бы заполнить недостающие записи соответствующими значениями даты и времени CEST / CET, чтобы преобразование прошло успешно, как в первом примере.
3. К сожалению, если вы не записали, было ли время до или после перехода, нет способа надежно устранить двусмысленность. #7225 отсутствует в файле CSV или Pandas удалил его?
4. примечание, если вы форматируете в строку, исходящую из даты и времени UTC, передайте эту информацию, используя формат, подобный
'%Y-%m-%d %H:%M:%SZ'
с Z, обозначающим UTC.5. @MarkRansom, значения borth 2017-10-29 02:00 (2-й вид) и 2017-10-29 03:00 отсутствуют в исходном файле. Они не были удалены. Я также думал о том, чтобы просто создать новый индекс CET, а затем переиндексировать, однако затем я получаю сообщение об ошибке, что переиндексация невозможна из-за дублирования значений из DST в октябре
Ответ №1:
Немного уточняю комментарий Марка Рэнсома;
2017-10-29 02:00,7224
неоднозначно; это может быть 2017-10-29 00:00 UTC
или 2017-10-29 01:00 UTC
. Вот почему pd.to_datetime отказывается что-либо выводить.
С помощью некоторого родного Python вы можете обойти это. Предполагая, что вы только что загрузили csv в df, ничего не анализируя в datetime, вы можете продолжить следующим образом
from datetime import datetime
import pytz
df['local_time'] = [pytz.timezone('Europe/Berlin').localize(datetime.fromisoformat(t)) for t in df['local_time']]
# so you can make a UTC index:
df.set_index(df['local_time'].dt.tz_convert('UTC'), inplace=True)
# Now you can create a new, hourly index from that and re-index:
dti = pd.date_range(df.index[0], df.index[-1], freq='H')
df2 = df.reindex(dti)
# for comparison, the "re-created" local_time column:
df2['local_time'] = df2.index.tz_convert('Europe/Berlin').strftime('%Y-%m-%d %H:%M:%S').values
это должно дать вам что-то вроде
df2
value local_time
2017-03-25 23:00:00 00:00 2016.0 2017-03-26 00:00:00
2017-03-26 00:00:00 00:00 2017.0 2017-03-26 01:00:00
2017-03-26 01:00:00 00:00 2018.0 2017-03-26 03:00:00
2017-03-26 02:00:00 00:00 2019.0 2017-03-26 04:00:00
2017-03-26 03:00:00 00:00 NaN 2017-03-26 05:00:00
... ...
2017-10-29 00:00:00 00:00 NaN 2017-10-29 02:00:00
2017-10-29 01:00:00 00:00 7224.0 2017-10-29 02:00:00 # note: value randomly attributed to "second" 2 am
2017-10-29 02:00:00 00:00 NaN 2017-10-29 03:00:00
2017-10-29 03:00:00 00:00 7226.0 2017-10-29 04:00:00
2017-10-29 04:00:00 00:00 7227.0 2017-10-29 05:00:00
Как указано выше, значение 7224
приписывается 2017-10-29 01:00:00 UTC
, но оно также может быть приписано 2017-10-29 00:00:00 UTC
, если вам все равно, все в порядке. Если это проблема, лучшее, что вы можете сделать, на мой взгляд, это отказаться от значения. Вы можете сделать это с помощью
df['local_time'] = pd.to_datetime(df['local_time']).dt.tz_localize('Europe/Berlin', ambiguous='NaT')
вместо родной части Python в приведенном выше коде.
Комментарии:
1. @MrFupppes, большое спасибо. Я понимаю. Я думаю, было бы здорово, если бы можно было отнести это к 2017-10-29 00:00:00 UTC, но я пока не нашел способа туда добраться.
2. @GregorJohnen вы можете применить это, установив
is_dst=True
при вызове метода localize ( docs ). Если вы зададите это ключевое слово, pytz будет локализован до времени, когда было активно летнее время, в случае неоднозначной даты / времени (как в вашем случае). Проблема, которую я вижу: это может сработать в данном конкретном случае, но не всегда может быть желаемым поведением…3. @Mr. Fuppes, что вы думаете о том, чтобы вместо этого работать с timedeltas, а затем переиндексировать на основе полного вектора CET, что позволит избежать проблем из-за дублирования оси в последнем? Это приведет к вставке NAN для отсутствующих столбцов в вектор значений. Послесловия можно снова преобразовать временные интервалы в datetime (CET), а затем локализовать столбец local time как UTC. Что вы думаете?
4. @MachineYogi на данный момент я не уверен, чем это будет отличаться от (строго монотонно возрастающего) столбца даты и времени UTC. Но если вы считаете, что это жизнеспособный подход и дает вам желаемый результат, почему бы не добавить его в качестве ответа?
5. @MachineYogi вы также должны создать локализованный столбец datetime с пониманием списка, но без синтаксического анализа из строки — например
df['local_time'] = [pytz.timezone('Europe/Berlin').localize(t) for t in df['local_time']]
Ответ №2:
Просто чтобы предоставить решение, которое я использую для этого обходного пути: он использует некоторые функции try: except: в случае неоднозначной ошибки времени. Это должно, с одной стороны, преобразовать временной вектор в UTC, а также заполнить недостающие значения путем переиндексации. Не стесняйтесь предлагать улучшения.
try: # here everything is as expected and one hour is missing in Mar and one hour is repeated in Oct
# Localize tz-naive index of the DataFrame to target time zone.
df['time'] = df.iloc[:,0].dt.tz_localize('CET', ambiguous='infer').dt.tz_convert('UTC').dt.strftime('%Y-%m-%d %H:%M:%S')
df = df.set_index(pd.to_datetime(df['time'], utc=True))
# Create a complete time vector in UTC for latter reindexing
idx = pd.date_range(df.index.min(), df.index.max(), freq=freq, tz='UTC')
# Verify that time vector is complete
if len(np.unique(np.diff(df.index))) == 1:
print('Time vector is complete!')
else:
# print dates which are not in the sequence and add them while simultaneously adding NaNs to the data columns
print(f'These dates are not in the sequence:{idx.difference(df["utc_time"])}')
df = df.reindex(idx).rename_axis('time')
except pytz.exceptions.AmbiguousTimeError: # here python does not know how to handle the non-reapeated time
# create the localized datetime column with a list comprehension
df['time'] = [pytz.timezone('Europe/Berlin').localize(t, is_dst=True) for t in df.iloc[:, 0]]
# make an UTC index:
df.set_index(df['time'].dt.tz_convert('UTC'), inplace=True)
# create a new index of desired frequency from that and re-index:
idx = pd.date_range(df.index[0], df.index[-1], freq=freq, tz='UTC')
# Verify that time vector is complete
if len(np.unique(np.diff(df.index))) == 1:
print('Time vector is complete!')
else:
# print dates which are not in the sequence and add them while simultaneously adding NaNs to the data columns
print(f'These were the dates which were not in the sequence:{pd.Series(idx.difference(df["time"]))}')
df = df.reindex(idx).rename_axis('time')