2015-07-14 10 views
5

In Python habe ich ein Array von Daten generiert (oder aus einer CSV-Datei lesen) mit Pandas, und ich möchte ein Jahr zu jedem Datum hinzufügen. Ich kann es mit Pandas arbeiten, aber nicht mit numpy. Was mache ich falsch? Oder ist es ein Fehler in Pandas oder numpy?numpy and pandas timedelta error

Danke!

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 

Antwort

5

Hinzufügen np.timedelta64(1, 'Y') zu einer Reihe von dtype datetime64[ns] nicht funktioniert, weil ein Jahr auf eine feste Anzahl von Nanosekunden nicht entspricht. Manchmal ist ein Jahr 365 Tage, manchmal 366 Tage, manchmal gibt es sogar eine zusätzliche Schaltsekunde. (Beachten Sie zusätzliche Schaltsekunden, wie die, die auf 2015.06.30 23:59:60 trat nicht als NumPy datetime64s darstellbaren.)

Der einfachste Weg, ich weiß, ein Jahr zu einem NumPy datetime64[ns] Array hinzufügen ist es in Bestandteile zu brechen, wie Jahre, Monate und Tage, auf integer-Arrays die Berechnung tun, und dann die datetime64 Array neu zusammenstellen:

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) 

Ausbeuten

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]') 

Trotz erscheinen, so zu sein viel Code, es ist tatsächlich schneller als ein DateOff hinzuzufügen Set von 1 Jahr:

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 

Natürlich pd.tseries.offsets bietet eine ganze Palette von Offsets, die keine einfachen Gegenstück haben, wenn sie mit NumPy datetime64s arbeiten.

+0

Ich bin erstaunt über die Geschwindigkeit und das Detail dieser Antwort! Da ich meine Daten mit Pandas lese, finde ich es einfacher, pandas DateOffset weiter zu verwenden, um Daten zu konvertieren, bevor ich meine Berechnungen mit numpy ausführe. Aber ich habe über eine Conversion nachgedacht, wie Sie es vorschlagen, also habe ich jetzt diesen Quellcode, wenn ich ihn brauche. Vielen Dank! – questiondude

1

Hier ist, was heißt es in der numpy documentation:

Es gibt zwei Timedelta-Einheiten ('Y', Jahre und 'M', Monate), die speziell behandelt werden, weil, wie viel Zeit Sie stellen Änderungen dar, je nachdem, wann sie verwendet werden. Während eine timedelta-Tageseinheit 24 Stunden entspricht, gibt es keine Möglichkeit, eine Monatseinheit in Tage umzurechnen, da die verschiedenen Monate unterschiedliche Tage haben.

Tage und Wochen scheinen allerdings zu arbeiten:

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

Ja, das habe ich auch in der Dokumentation gelesen. Es hat meine Verwirrung jedoch nicht geklärt :-) – questiondude