Dodaj kolumnę z liczbą dni między datami w pandach DataFrame

101

Chcę odjąć daty w „A” od dat w „B” i dodać nową kolumnę z różnicą.

df
          A        B
one 2014-01-01  2014-02-28 
two 2014-02-03  2014-03-01

Próbowałem wykonać następujące czynności, ale pojawia się błąd, gdy próbuję uwzględnić to w pętli for ...

import datetime
date1=df['A'][0]
date2=df['B'][0]
mdate1 = datetime.datetime.strptime(date1, "%Y-%m-%d").date()
rdate1 = datetime.datetime.strptime(date2, "%Y-%m-%d").date()
delta =  (mdate1 - rdate1).days
print delta

Co powinienem zrobić?

Jase Villam
źródło

Odpowiedzi:

100

Zakładając, że były to kolumny z datą i godziną (jeśli nie mają zastosowania to_datetime), możesz je po prostu odjąć:

df['A'] = pd.to_datetime(df['A'])
df['B'] = pd.to_datetime(df['B'])

In [11]: df.dtypes  # if already datetime64 you don't need to use to_datetime
Out[11]:
A    datetime64[ns]
B    datetime64[ns]
dtype: object

In [12]: df['A'] - df['B']
Out[12]:
one   -58 days
two   -26 days
dtype: timedelta64[ns]

In [13]: df['C'] = df['A'] - df['B']

In [14]: df
Out[14]:
             A          B        C
one 2014-01-01 2014-02-28 -58 days
two 2014-02-03 2014-03-01 -26 days

Uwaga: upewnij się, że używasz nowej pandy (np. 0.13.1), może to nie działać w starszych wersjach.

Andy Hayden
źródło
24
Czy możemy pozbyć się części „dni” w wyniku, gdybyśmy po prostu musieli zobaczyć wartość liczbową, tj. -58, -26 w tym przypadku.
0nir
6
aby rozwinąć komentarz @AndyHayden, który działa, ale powinien pd.offsets.Day(1)(z „s”). Ja też zwykle to (df['A'] - df['B']) / pd.offsets.Day(-1)
neguję
12
Jeśli jednak chcesz to zrobić w całej serii, potrzebujesz (df['A'] - df['B']) / np.timedelta64(-1, 'D')powodów, których nie do końca rozumiem.
dirkjot
@dirkjot Dzięki za wykrycie literówki! IIRC to zostało naprawione w ostatnich pandach, czy używasz wersji 0.16.2 / 0.17?
Andy Hayden
2
@webelo sam DatetimeIndex / Series powinien mieć .dt.daysatrybut, który powinien być zdecydowanie preferowany.
Andy Hayden
109

Aby usunąć element tekstowy „days”, możesz również skorzystać z akcesorium dt () dla serii: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.dt.html

Więc,

df[['A','B']] = df[['A','B']].apply(pd.to_datetime) #if conversion required
df['C'] = (df['B'] - df['A']).dt.days

która zwraca:

             A          B   C
one 2014-01-01 2014-02-28  58
two 2014-02-03 2014-03-01  26
Ricky McMaster
źródło
3
Świetna odpowiedź. W moim przypadku df['C'] = (df['B'] - df['A']).dt.daysnie zadziałało i musiałem skorzystać df['C'] = (df['B'] - df['A']).days. Masz jakiś pomysł, dlaczego mój nie podał liczby dni zgodnie z oczekiwaniami?
Samuel Nde
Nde - jak dokładnie to nie zadziałało? Błąd lub złe wartości? Czy udało Ci się przekonwertować kolumny A i B na datę / godzinę?
Ricky McMaster
1
Obie moje kolumny są datą i godziną (a datetime64[ns]dokładniej). Kiedy to zrobiłem df['C'] = (df['B'] - df['A']).dt.days, pojawił się błąd atrybutu, który mówił o błędzie AttributeError: Obiekt „Timedelta” nie ma atrybutu „dt” , więc próbowałem df [„C”] = (df [„B”] - df [„A”]). dni, które dały mi pożądaną odpowiedź. (Oczywiście używam własnej ramki danych, a nie tej z powyższego przykładu. A może dlatego, że mam czas na randkę, a nie jak w 2018-09-24 10:17:18.800277)
Samuel Nde
1
doskonała odpowiedź.
user3065757
1
Świetne rozwiązanie. Dzięki!
Rodrigo Hjort
11

Zrozumienie listy jest najlepszym sposobem na najbardziej Pythonic (i najszybszy) sposób, aby to zrobić:

[int(i.days) for i in (df.B - df.A)]
  1. zwrócę opóźnienie (np. „-58 dni”)
  2. i.days zwróci tę wartość jako długą liczbę całkowitą (np. -58L)
  3. int (i.days) da ci -58, którego szukasz.

Jeśli Twoje kolumny nie są w formacie daty i godziny. Krótsza składnia to:df.A = pd.to_datetime(df.A)

A.Kot
źródło
1

Co powiesz na to:

times['days_since'] = max(list(df.index.values))  
times['days_since'] = times['days_since'] - times['months']  
times
Tomek
źródło