Преобразование даты строки в эпоху, не работающей с библиотеками Cython и POSIX C

У меня очень большой фрейм панд и я хотел бы создать столбец, содержащий время в секундах с момента создания строки даты формата ISO-8601.

Первоначально я использовал стандартные библиотеки Python для этого, но результат довольно медленный. Я попытался заменить это, используя функции библиотеки strptime c strptime и mktime напрямую, но не смог получить правильный ответ для преобразования времени.

Вот код (для запуска в окне IPython)

 %load_ext cythonmagic %%cython from posix.types cimport time_t cimport numpy as np import numpy as np import time cdef extern from "sys/time.h" nogil: struct tm: int tm_sec int tm_min int tm_hour int tm_mday int tm_mon int tm_year int tm_wday int tm_yday int tm_isdst time_t mktime(tm *timeptr) char *strptime(const char *s, const char *format, tm *tm) cdef to_epoch_c(const char *date_text): cdef tm time_val strptime(date_text, "%Y-%m-%d", &time_val) return <unsigned int>mktime(&time_val) cdef to_epoch_py(const char *date_text): return np.uint32(time.mktime(time.strptime(date_text, "%Y-%m-%d"))) cpdef np.ndarray[unsigned int] apply_epoch_date_c(np.ndarray col_date): cdef Py_ssize_t i, n = len(col_date) cdef np.ndarray[unsigned int] res = np.empty(n, dtype=np.uint32) for i in range(len(col_date)): res[i] = to_epoch_c(col_date[i]) return res cpdef np.ndarray[unsigned int] apply_epoch_date_py(np.ndarray col_date): cdef Py_ssize_t i, n = len(col_date) cdef np.ndarray[unsigned int] res = np.empty(n, dtype=np.uint32) for i in range(len(col_date)): res[i] = to_epoch_py(col_date[i]) return res 

strptime созданная strptime , не выглядит так, как мне кажется, часы, минуты и секунды слишком велики, удаление их или установка их на 0, похоже, не дает ответа, который я ищу.

Вот небольшой тест df, который показывает, что значения не подходят для метода c:

 from pandas import DataFrame test = DataFrame({'date_text':["2015-05-18" for i in range(3)]}, dtype=np.uint32) apply_epoch_date_py(test['date_text'].values) Output: array([1431903600, 1431903600, 1431903600], dtype=uint32) apply_epoch_date_c(test['date_text'].values) Output: array([4182545380, 4182617380, 4182602980], dtype=uint32) 

Я не понимаю, почему значения для версии c не всегда одинаковы и настолько далеки от того, какими они должны быть. Я надеюсь, что ошибка достаточно мала, потому что разница во времени между этими двумя на большом фрейме данных существенна (я не уверен, насколько меньше работает версия c, поскольку она не работает должным образом)

 test_large = DataFrame({'date_text':["2015-05-18" for i in range(int(10e6))]}, dtype=np.uint32) %timeit -n 1 -r 1 apply_epoch_date_py(test_large['date_text'].values) Output: 1 loops, best of 1: 1min 58s per loop %timeit apply_epoch_date_c(test_large['date_text'].values) Output: 1 loops, best of 3: 5.59 s per loop 

Я просмотрел этот пост cython time.h и общее время c unix из строкового сообщения, которое может быть полезно для кого-то, отвечающего.

Поэтому мой главный вопрос касается функции to_epoch_c почему эта функция to_epoch_c неверные значения? благодаря

Обновить:

Метод @Jeff действительно самый быстрый и простой способ решения этой проблемы с использованием панд.

Производительность strptime / mktime в Python в бедных по сравнению с другими методами. Другой упомянутый здесь метод на основе Python намного быстрее. Выполнение преобразования для всех методов, упомянутых в этом сообщении (плюс pd.to_datetime с pd.to_datetime строковым форматом), дает интересные результаты. Панды с infer_datetime_format легко быстрее, масштабируются очень хорошо. Немного неинтуитивно, если вы сообщите панде, что формат даты, это намного медленнее.

Сравнение производительности

Сравнение профилей обоих методов панды:

 %prun -l 3 pd.to_datetime(df['date_text'],infer_datetime_format=True, box=False).values.view('i8')/10**9 352 function calls (350 primitive calls) in 0.021 seconds Ordered by: internal time List reduced from 96 to 3 due to restriction <3> ncalls tottime percall cumtime percall filename:lineno(function) 1 0.013 0.013 0.013 0.013 {pandas.tslib.array_to_datetime} 1 0.005 0.005 0.005 0.005 {pandas.lib.isnullobj} 1 0.001 0.001 0.021 0.021 <string>:1(<module>) %prun -l 3 pd.to_datetime(df['date_text'],format="%Y-%m-%d", box=False).values.view('i8')/10**9 109 function calls (107 primitive calls) in 0.253 seconds Ordered by: internal time List reduced from 55 to 3 due to restriction <3> ncalls tottime percall cumtime percall filename:lineno(function) 1 0.251 0.251 0.251 0.251 {pandas.tslib.array_strptime} 1 0.001 0.001 0.253 0.253 <string>:1(<module>) 1 0.000 0.000 0.252 0.252 tools.py:176(to_datetime) 

2 Solutions collect form web for “Преобразование даты строки в эпоху, не работающей с библиотеками Cython и POSIX C”

Кажется, что если вы не проходите в time_val.tm_hour, time_val.tm_min и time_val.tm_sec то дата анализируется некорректно, установка значений в 0 возвращает правильную метку времени:

 cdef extern from "sys/time.h" nogil: struct tm: int tm_sec #Seconds [0,60]. int tm_min #Minutes [0,59]. int tm_hour #Hour [0,23]. int tm_mday #Day of month [1,31]. int tm_mon #Month of year [0,11]. int tm_year #Years since 1900. int tm_wday #Day of week [0,6] (Sunday =0). int tm_yday #Day of year [0,365]. int tm_isdst #Daylight Savings time_t mktime(tm *timeptr) char *strptime(const char *s, const char *format, tm *tm) cdef to_epoch_c(const char *date_text): cdef tm time_val time_val.tm_hour, time_val.tm_min, time_val.tm_sec= 0, 0, 0 strptime(date_text, "%Y-%m-%d", &time_val) return <unsigned int>mktime(&time_val) 

Если вы print(time.strptime(date_text, "%Y-%m-%d")) вы видите, что python имеет значения, равные 0 если вы не передаете их в strptime:

  time.struct_time(tm_year=2015, tm_mon=5, tm_mday=18, tm_hour=12, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=138, tm_isdst=-1) 

Установка значений по умолчанию 0 в to_epoch_c также возвращает 0 :

 {'tm_sec': 0, 'tm_hour': 0, 'tm_mday': 18, 'tm_isdst': 1, 'tm_year': 115, 'tm_mon': 4, 'tm_yday': 137, 'tm_wday': 1, 'tm_min': 0} 

Если вы не установите их в случайные временные метки возврата, потому что, как представляется, существуют различные значения для tm_sec т. Д.:

  {'tm_sec': -1437999996, 'tm_hour': 0, 'tm_mday': 0, 'tm_isdst': -1438000080, 'tm_year': 32671, 'tm_mon': -1412460224, 'tm_yday': 0, 'tm_wday': 5038405, 'tm_min': 32671} {'tm_sec': -1437999996, 'tm_hour': 4, 'tm_mday': 14, 'tm_isdst': 0, 'tm_year': 69, 'tm_mon': 10, 'tm_yday': 317, 'tm_wday': 5, 'tm_min': 32671} {'tm_sec': -1437999996, 'tm_hour': 9, 'tm_mday': 14, 'tm_isdst': 0, 'tm_year': 69, 'tm_mon': 10, 'tm_yday': 317, 'tm_wday': 5, 'tm_min': 32671} 

Я полагаю, что, возможно, python обрабатывает, когда вы не передаете их в чем-то подобном, но я не смотрел на источник, поэтому, возможно, кто-то более опытный в c подтвердит.

Если вы попытаетесь передать менее 9 элементов на time.time_struct вы получите сообщение об ошибке, которое несколько подтверждает то, что я думал:

 In [60]: import time In [61]: struct = time.struct_time((2015, 6, 18)) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-61-ee40483c37d4> in <module>() ----> 1 struct = time.struct_time((2015, 6, 18)) TypeError: time.struct_time() takes a 9-sequence (3-sequence given) 

Вы должны передать последовательность из 9 элементов:

 In [63]: struct = time.struct_time((2015, 6, 18, 0, 0, 0, 0, 0, 0)) In [64]: struct Out[65]: time.struct_time(tm_year=2015, tm_mon=6, tm_mday=18, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=0, tm_isdst=0) 

В любом случае с изменениями вы получаете одинаковое поведение в обоих:

 In [16]: import pandas as pd In [17]: import numpy as np In [18]: test = pd.DataFrame({'date_text' : ["2015-05-18" for i in range(3)]}, dtype=np.uint32) In [19]: apply_epoch_date_c(test['date_text'].values) Out[19]: array([1431903600, 1431903600, 1431903600], dtype=uint32) In [20]: apply_epoch_date_py(test['date_text'].values) Out[20]: array([1431903600, 1431903600, 1431903600], dtype=uint32) 

Некоторые тесты на каждую дату с 1970-1-1 показывают, что оба возвращают одни и те же метки времени:

 In [55]: from datetime import datetime, timedelta In [56]: tests = np.array([(datetime.strptime("1970-1-1","%Y-%m-%d")+timedelta(i)).strftime("%Y-%m-%d") for i in range(16604)]) In [57]: a = apply_epoch_date_c( tests) In [58]: b = apply_epoch_date_py( tests) In [59]: for d1,d2 in zip(a,b): assert d1 == d1 ....: In [60]: 

Сроки реализации обоих реализаций кода на самом деле были немного более эффективными:

 In [21]: timeit apply_epoch_date_py(test['date_text'].values) 10000 loops, best of 3: 73 µs per loop In [22]: timeit apply_epoch_date_c(test['date_text'].values) 100000 loops, best of 3: 10.8 µs per loop 

Простой метод чистых панд. Даты хранятся изначально как i8 (в ns с эпохи).

 In [30]: df = DataFrame({'date_text':["2015-05-18" for i in range(int(10e6))]}, dtype=np.uint32) In [31]: df.info() <class 'pandas.core.frame.DataFrame'> Int64Index: 10000000 entries, 0 to 9999999 Data columns (total 1 columns): date_text object dtypes: object(1) memory usage: 152.6+ MB In [32]: pd.to_datetime(df['date_text'],infer_datetime_format=True, box=False).values.view('i8')/10**9 Out[32]: array([1431907200, 1431907200, 1431907200, ..., 1431907200, 1431907200, 1431907200]) In [33]: %timeit pd.to_datetime(df['date_text'],infer_datetime_format=True, box=False).values.view('i8')/10**9 1 loops, best of 3: 1.96 s per loop 
  • Как перенаправить все методы содержащегося класса в Python?
  • Травление DataFrame
  • Pandon pandas: заполнить строку данных по строкам
  • python pandas timeseries plot, как установить xlim и xticks за пределами ts.plot ()?
  • Рассчитать расстояние до ближайшей точки с помощью геопандов
  • Pandas Dataframe: разбиение нескольких столбцов на несколько столбцов
  • Pandas Эквивалент R, который ()
  • Объединить две строки в том же Dataframe, если их индекс одинаковый?
  • Python - лучший язык программирования в мире.