Перетворити для часового поясу панд DateTimeIndex на наївну мітку часу, але в певному часовому поясі


99

Ви можете використовувати функцію, tz_localizeщоб зробити часовий пояс або часовий пояс DateTimeIndex відомим, але як ви можете зробити навпаки: як ви можете перетворити часовий пояс, відомий часовому поясу, у наївний, зберігаючи свій часовий пояс?

Приклад:

In [82]: t = pd.date_range(start="2013-05-18 12:00:00", periods=10, freq='s', tz="Europe/Brussels")

In [83]: t
Out[83]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: S, Timezone: Europe/Brussels

Я міг видалити часовий пояс, встановивши для нього значення None, але тоді результат перетворюється на UTC (12 годин став 10):

In [86]: t.tz = None

In [87]: t
Out[87]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 10:00:00, ..., 2013-05-18 10:00:09]
Length: 10, Freq: S, Timezone: None

Чи є інший спосіб перетворити DateTimeIndex на наївний часовий пояс, але при збереженні часового поясу, в якому він був встановлений?


Деякий контекст причини, з якої я запитую це: я хочу працювати з наївними часовими поясами часових поясів (щоб уникнути зайвих клопотів з часовими поясами, і вони мені не потрібні для випадку, над яким я працюю).
Але з якихось причин мені доводиться мати справу з часовими поясами, що відповідають часовому поясу, у моєму місцевому часовому поясі (Європа / Брюссель). Оскільки всі мої інші дані є часовими поясами (але представлені в моєму місцевому часовому поясі), я хочу перетворити цей часовий ряд на наївний для подальшої роботи з ним, але він також повинен бути представлений у моєму місцевому часовому поясі (тому просто видаліть інформацію про часовий пояс, без перетворення видимого для користувача часу на UTC).

Я знаю, що час насправді внутрішньо зберігається як UTC і перетворюється в інший часовий пояс лише тоді, коли ви його представляєте, тому має бути якесь перетворення, коли я хочу його "ділокалізувати". Наприклад, за допомогою модуля python datetime ви можете "видалити" часовий пояс так:

In [119]: d = pd.Timestamp("2013-05-18 12:00:00", tz="Europe/Brussels")

In [120]: d
Out[120]: <Timestamp: 2013-05-18 12:00:00+0200 CEST, tz=Europe/Brussels>

In [121]: d.replace(tzinfo=None)
Out[121]: <Timestamp: 2013-05-18 12:00:00> 

Отже, виходячи з цього, я міг би зробити наступне, але, гадаю, це буде не надто ефективно при роботі з більшими часовими рядами:

In [124]: t
Out[124]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: S, Timezone: Europe/Brussels

In [125]: pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
Out[125]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: None, Timezone: None

Часовий пояс = Немає означає UTC ... Я не впевнений, що розумію, про що Ви тут питаєте.
Andy Hayden

Я додав якесь пояснення. Я хочу зберегти час, який ви бачите як користувача. Сподіваюсь, це трохи прояснить.
joris

А-а-а, справді, я не розумів, що ти можеш це зробити replace.
Енді Хайден,

@AndyHayden Отже, насправді те, що я хочу, - це саме те зворотне tz_localize, що replace(tzinfo=None)робить для дат, але це дійсно не дуже очевидний спосіб.
joris

Відповіді:


123

Щоб відповісти на власне запитання, тим часом ця функція була додана до панд. Починаючи з pandas 0.15.0 , ви можете використовувати tz_localize(None)для видалення часовий пояс, що призводить до місцевого часу.
Дивіться запис whatsnew: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improvements

Отже, з моїм прикладом зверху:

In [4]: t = pd.date_range(start="2013-05-18 12:00:00", periods=2, freq='H',
                          tz= "Europe/Brussels")

In [5]: t
Out[5]: DatetimeIndex(['2013-05-18 12:00:00+02:00', '2013-05-18 13:00:00+02:00'],
                       dtype='datetime64[ns, Europe/Brussels]', freq='H')

використання tz_localize(None)видаляє інформацію про часовий пояс, що призводить до наївного місцевого часу :

In [6]: t.tz_localize(None)
Out[6]: DatetimeIndex(['2013-05-18 12:00:00', '2013-05-18 13:00:00'], 
                      dtype='datetime64[ns]', freq='H')

Крім того, ви також можете використовувати tz_convert(None)для видалення інформації про часовий пояс, але перетворюючи на UTC, таким чином отримуючи наївний час UTC :

In [7]: t.tz_convert(None)
Out[7]: DatetimeIndex(['2013-05-18 10:00:00', '2013-05-18 11:00:00'], 
                      dtype='datetime64[ns]', freq='H')

Це набагато більш продуктивним , ніж datetime.replaceрішення:

In [31]: t = pd.date_range(start="2013-05-18 12:00:00", periods=10000, freq='H',
                           tz="Europe/Brussels")

In [32]: %timeit t.tz_localize(None)
1000 loops, best of 3: 233 µs per loop

In [33]: %timeit pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
10 loops, best of 3: 99.7 ms per loop

1
У разі , якщо ви працюєте з чим - то , що вже знаходиться UTC і необхідності перетворити його в місцевий час і потім впустити часовий пояс: from tzlocal import get_localzone, tz_here = get_localzone(),<datetime object>.tz_convert(tz_here).tz_localize(None)
Натан Ллойд

3
Якщо у вас немає корисного індексу, вам може знадобитися t.dt.tz_localize(None)або t.dt.tz_convert(None). Зверніть увагу на .dt.
Прозріння

2
Це рішення працює лише тоді, коли в серії є один унікальний тз. Якщо у вас є кілька різних TZ в одній і тій же серії, то побачити (і upvote) рішення тут :-): stackoverflow.com/a/59204751/1054154
tozCSS

14

Я думаю, ви не можете досягти бажаного більш ефективно, ніж ви пропонували.

Основна проблема полягає в тому, що відмітки часу (як вам здається відомо) складаються з двох частин. Дані, що представляють час UTC та часовий пояс tz_info. Інформація про часовий пояс використовується лише для відображення під час друку часового поясу на екрані. Під час відображення дані компенсуються належним чином і +01: 00 (або подібне) додається до рядка. Видалення значення tz_info (за допомогою tz_convert (tz = None)) насправді не змінює дані, що представляють наївну частину позначки часу.

Отже, єдиний спосіб зробити те, що ви хочете, - це змінити базові дані (панди не дозволяють цього ... DatetimeIndex незмінні - див. Довідку на DatetimeIndex) або створити новий набір об’єктів часової мітки та обернути їх у новому DatetimeIndex. Ваше рішення робить останнє:

pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])

Для довідки, ось replaceметод Timestamp(див. Tslib.pyx):

def replace(self, **kwds):
    return Timestamp(datetime.replace(self, **kwds),
                     offset=self.offset)

Ви можете звернутися до документів на, datetime.datetimeщоб побачити, що datetime.datetime.replaceтакож створює новий об’єкт.

Якщо ви можете, найкращим варіантом для ефективності є модифікація джерела даних таким чином, щоб воно (неправильно) повідомляло мітки часу без часового поясу. Ви згадали:

Я хочу працювати з наївними часовими поясами часових поясів (щоб уникнути зайвих клопотів з часовими поясами, і вони мені не потрібні для випадку, над яким я працюю)

Мені було б цікаво, про які зайві клопоти ви маєте на увазі. Я рекомендую, як загальне правило для всієї розробки програмного забезпечення, зберігати свою позначку часу "наївні значення" в UTC. Існує трохи гірше, ніж розглядати дві різні цінності int64, задаючись питанням, до якого часового поясу вони належать. Якщо ви завжди, завжди, завжди використовуєте UTC для внутрішньої пам’яті, то ви уникнете незліченних головних болів. Моя мантра - це часові пояси, призначені лише для введення / виводу людини .


3
Дякую за відповідь та пізню відповідь: мій випадок - це не заявка, а лише науковий аналіз моєї власної роботи (тому, наприклад, відсутність обміну інформацією з співавторами по всьому світу). І в цьому випадку може бути простіше просто працювати з наївними позначками часу, але за місцевим часом. Тому мені не потрібно турбуватися про часові пояси, і я просто можу інтерпретувати часову позначку як місцевий час (додатковою "клопотом" може бути, наприклад, що все тоді повинно бути в часових поясах, інакше ви отримуєте такі речі, як "не можна порівняти офсет- наївні та з урахуванням компенсації дати "). Але я повністю згоден з вами, коли маю справу з більш складними програмами.
joris

12

Оскільки я завжди намагаюся пам’ятати, короткий підсумок того, що робить кожен із них:

>>> pd.Timestamp.now()  # naive local time
Timestamp('2019-10-07 10:30:19.428748')

>>> pd.Timestamp.utcnow()  # tz aware UTC
Timestamp('2019-10-07 08:30:19.428748+0000', tz='UTC')

>>> pd.Timestamp.now(tz='Europe/Brussels')  # tz aware local time
Timestamp('2019-10-07 10:30:19.428748+0200', tz='Europe/Brussels')

>>> pd.Timestamp.now(tz='Europe/Brussels').tz_localize(None)  # naive local time
Timestamp('2019-10-07 10:30:19.428748')

>>> pd.Timestamp.now(tz='Europe/Brussels').tz_convert(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')

>>> pd.Timestamp.utcnow().tz_localize(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')

>>> pd.Timestamp.utcnow().tz_convert(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')

7

Встановлення tzатрибута індексу явно працює:

ts_utc = ts.tz_convert("UTC")
ts_utc.index.tz = None

3
Пізній коментар, але я хочу, щоб результатом був час, представлений у місцевому часовому поясі, а не в UTC. І як я показую у питанні, встановлення значення tzNone також перетворює його на UTC.
joris

Крім того, часові ряди вже знають часовий пояс, тому заклик tz_convertдо нього спричинить помилку.
joris

4

Прийняте рішення не працює, коли в серії є кілька різних часових поясів. Це кидаєValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

Рішення полягає у використанні applyметоду.

Будь ласка, перегляньте приклади нижче:

# Let's have a series `a` with different multiple timezones. 
> a
0    2019-10-04 16:30:00+02:00
1    2019-10-07 16:00:00-04:00
2    2019-09-24 08:30:00-07:00
Name: localized, dtype: object

> a.iloc[0]
Timestamp('2019-10-04 16:30:00+0200', tz='Europe/Amsterdam')

# trying the accepted solution
> a.dt.tz_localize(None)
ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

# Make it tz-naive. This is the solution:
> a.apply(lambda x:x.tz_localize(None))
0   2019-10-04 16:30:00
1   2019-10-07 16:00:00
2   2019-09-24 08:30:00
Name: localized, dtype: datetime64[ns]

# a.tz_convert() also does not work with multiple timezones, but this works:
> a.apply(lambda x:x.tz_convert('America/Los_Angeles'))
0   2019-10-04 07:30:00-07:00
1   2019-10-07 13:00:00-07:00
2   2019-09-24 08:30:00-07:00
Name: localized, dtype: datetime64[ns, America/Los_Angeles]

3

Спираючись на пропозицію DA, що " єдиним способом зробити те, що ви хочете, є модифікація базових даних " та використанням numpy для зміни базових даних ...

Це працює для мене, і це досить швидко:

def tz_to_naive(datetime_index):
    """Converts a tz-aware DatetimeIndex into a tz-naive DatetimeIndex,
    effectively baking the timezone into the internal representation.

    Parameters
    ----------
    datetime_index : pandas.DatetimeIndex, tz-aware

    Returns
    -------
    pandas.DatetimeIndex, tz-naive
    """
    # Calculate timezone offset relative to UTC
    timestamp = datetime_index[0]
    tz_offset = (timestamp.replace(tzinfo=None) - 
                 timestamp.tz_convert('UTC').replace(tzinfo=None))
    tz_offset_td64 = np.timedelta64(tz_offset)

    # Now convert to naive DatetimeIndex
    return pd.DatetimeIndex(datetime_index.values + tz_offset_td64)

Дякую за вашу відповідь! Однак, я думаю, це буде працювати лише в тому випадку, якщо в період набору даних не буде переходу на літній / зимовий час.
joris

@joris Ах, хороший улов! Я цього не враховував! Я зміню своє рішення, щоб якомога швидше вирішити цю ситуацію.
Jack Kelly

Я вважаю, що це все ще неправильно, оскільки ви обчислюєте компенсацію лише вперше, а не по мірі її прогресу протягом часу. Це призведе до того, що ви пропустите перехід на літній час та не налаштуєте відповідне значення на дану дату та далі.
П'єр-Люк Бертран,

2

Пізній внесок, але щойно натрапив на щось подібне в Python datetime, а панди дають різні відмітки часу для тієї самої дати .

Якщо у вас встановлений часовий пояс pandas, час технічно tz_localize(None)змінює позначку часу POSIX (яка використовується всередині) так, ніби місцевим часом із позначки часу було UTC. Місцевий у цьому контексті означає місцевий у вказаному часовому поясі . Приклад:

import pandas as pd

t = pd.date_range(start="2013-05-18 12:00:00", periods=2, freq='H', tz="US/Central")
# DatetimeIndex(['2013-05-18 12:00:00-05:00', '2013-05-18 13:00:00-05:00'], dtype='datetime64[ns, US/Central]', freq='H')

t_loc = t.tz_localize(None)
# DatetimeIndex(['2013-05-18 12:00:00', '2013-05-18 13:00:00'], dtype='datetime64[ns]', freq='H')

# offset in seconds according to timezone:
(t_loc.values-t.values)//1e9
# array([-18000, -18000], dtype='timedelta64[ns]')

Зауважте, що це залишатиме вас дивними речами під час переходу на літній час , наприклад

t = pd.date_range(start="2020-03-08 01:00:00", periods=2, freq='H', tz="US/Central")
(t.values[1]-t.values[0])//1e9
# numpy.timedelta64(3600,'ns')

t_loc = t.tz_localize(None)
(t_loc.values[1]-t_loc.values[0])//1e9
# numpy.timedelta64(7200,'ns')

На відміну від цього, tz_convert(None)не змінює внутрішню позначку часу, вона просто видаляє tzinfo.

t_utc = t.tz_convert(None)
(t_utc.values-t.values)//1e9
# array([0, 0], dtype='timedelta64[ns]')

Моїм підсумком було б: дотримуйтесь часового поясу, якщо ви можете або використовуєте лише те, t.tz_convert(None)що не змінює базову позначку часу POSIX. Тільки майте на увазі, що тоді ви практично працюєте з UTC.

(Python 3.8.2 x64 у Windows 10, pandasv1.0.5.)


0

Найголовніше - це додати, tzinfoколи ви визначаєте об'єкт дати та часу.

from datetime import datetime, timezone
from tzinfo_examples import HOUR, Eastern
u0 = datetime(2016, 3, 13, 5, tzinfo=timezone.utc)
for i in range(4):
     u = u0 + i*HOUR
     t = u.astimezone(Eastern)
     print(u.time(), 'UTC =', t.time(), t.tzname())
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.