2015-07-14 1 views
5

W Pythonie mam tablicę dat wygenerowanych (lub odczytanych z pliku CSV) za pomocą pand i chcę dodać jeden rok do każdej daty. Mogę sprawić, żeby działało, używając pand, ale nie używając numpy. Co ja robię źle? Czy jest to błąd w pandach lub numpy?błąd numed i pandy timedelta

Dzięki!

import numpy as np 
import pandas as pd 
from pandas.tseries.offsets import DateOffset 

# Generate range of dates using pandas. 
dates = pd.date_range('1980-01-01', '2015-01-01') 

# Add one year using pandas. 
dates2 = dates + DateOffset(years=1) 

# Convert result to numpy. THIS WORKS! 
dates2_np = dates2.values 

# Convert original dates to numpy array. 
dates_np = dates.values 

# Add one year using numpy. THIS FAILS! 
dates3 = dates_np + np.timedelta64(1, 'Y') 

# TypeError: Cannot get a common metadata divisor for NumPy datetime metadata [ns] and [Y] because they have incompatible nonlinear base time units 

Odpowiedz

5

Dodawanie np.timedelta64(1, 'Y') do tablicy dtype datetime64[ns] nie działa, ponieważ rok nie odpowiada ustaloną liczbę nanosekund. Czasami rok to 365 dni, czasem 366 dni, czasami jest nawet dodatkowy sekundowy skok. (Uwaga dodatkowe sekundy przestępne, takie jak ten, który wystąpił w dniu 2015-06-30 23:59:60, nie są reprezentowalne jako NumPy datetime64s.)

Najprostszy sposób, jaki znam, aby dodać rok do tablicy NumPy datetime64[ns] jest podzielenie go na części składowe, takie jak lat, miesięcy i dni, do obliczeń na tablicach liczbę całkowitą, a następnie przekomponujesz datetime64 tablicy:

def year(dates): 
    "Return an array of the years given an array of datetime64s" 
    return dates.astype('M8[Y]').astype('i8') + 1970 

def month(dates): 
    "Return an array of the months given an array of datetime64s" 
    return dates.astype('M8[M]').astype('i8') % 12 + 1 

def day(dates): 
    "Return an array of the days of the month given an array of datetime64s" 
    return (dates - dates.astype('M8[M]'))/np.timedelta64(1, 'D') + 1 

def combine64(years, months=1, days=1, weeks=None, hours=None, minutes=None, 
       seconds=None, milliseconds=None, microseconds=None, nanoseconds=None): 
    years = np.asarray(years) - 1970 
    months = np.asarray(months) - 1 
    days = np.asarray(days) - 1 
    types = ('<M8[Y]', '<m8[M]', '<m8[D]', '<m8[W]', '<m8[h]', 
      '<m8[m]', '<m8[s]', '<m8[ms]', '<m8[us]', '<m8[ns]') 
    vals = (years, months, days, weeks, hours, minutes, seconds, 
      milliseconds, microseconds, nanoseconds) 
    return sum(np.asarray(v, dtype=t) for t, v in zip(types, vals) 
       if v is not None) 

# break the datetime64 array into constituent parts 
years, months, days = [f(dates_np) for f in (year, month, day)] 
# recompose the datetime64 array after adding 1 to the years 
dates3 = combine64(years+1, months, days) 

plony

In [185]: dates3 
Out[185]: 
array(['1981-01-01', '1981-01-02', '1981-01-03', ..., '2015-12-30', 
     '2015-12-31', '2016-01-01'], dtype='datetime64[D]') 

Pomimo pojawiające się tak dużo kodu, jest to rzeczywiście szybsze niż dodanie DateOff zestaw 1 roku:

In [206]: %timeit dates + DateOffset(years=1) 
1 loops, best of 3: 285 ms per loop 

In [207]: %%timeit 
    .....: years, months, days = [f(dates_np) for f in (year, month, day)] 
    .....: combine64(years+1, months, days) 
    .....: 
100 loops, best of 3: 2.65 ms per loop 

Oczywiście pd.tseries.offsets oferuje cały wachlarz przesunięć, które nie mają odpowiednika łatwe podczas pracy z datetime64s NumPy.

+0

Jestem zaskoczony szybkością i szczegółowością tej odpowiedzi! Ponieważ czytam moje dane przy użyciu pand, łatwiej jest dalej używać pand "DateOffset do konwertowania dat, zanim wykonam obliczenia za pomocą numpy. Ale myślałem o zrobieniu konwersji, jak sugerujesz, więc teraz mam ten kod źródłowy, jeśli będę go potrzebować. Dzięki! – questiondude

1

Oto co mówi w numpy documentation:

Istnieją dwie jednostki timedelta ('Y', lata i 'M', miesiąc), które są traktowane specjalnie, bo ile czasu reprezentują zmiany w zależności od tego, kiedy są używane. Podczas gdy jednostka z timedelta odpowiada 24 godzinom, nie ma możliwości przekonwertowania jednostki miesiąca na dni, ponieważ różne miesiące mają różną liczbę dni.

Dni i tygodnie wydają się działać jednak:

dates4 = dates_np + np.timedelta64(1, 'D') 
dates5 = dates_np + np.timedelta64(1, 'W') 
+0

Tak, przeczytałem to również w dokumentacji. Nie wyjaśniło to jednak mojego zamieszania :-) – questiondude