Tambahkan tanggal yang hilang ke bingkai data panda

128

Data saya dapat memiliki beberapa acara pada tanggal tertentu atau NO acara pada tanggal tertentu. Saya mengambil peristiwa ini, menghitung berdasarkan tanggal dan memplotnya. Namun, ketika saya memplotnya, kedua seri saya tidak selalu cocok.

idx = pd.date_range(df['simpleDate'].min(), df['simpleDate'].max())
s = df.groupby(['simpleDate']).size()

Dalam kode di atas idx menjadi kisaran katakanlah 30 tanggal. 09-01-2013 hingga 09-30-2013 Namun S mungkin hanya memiliki 25 atau 26 hari karena tidak ada peristiwa yang terjadi untuk tanggal tertentu. Saya kemudian mendapatkan AssertionError karena ukurannya tidak cocok ketika saya mencoba merencanakan:

fig, ax = plt.subplots()    
ax.bar(idx.to_pydatetime(), s, color='green')

Apa cara yang tepat untuk mengatasi ini? Apakah saya ingin menghapus tanggal tanpa nilai dari IDX atau (yang lebih saya lakukan) adalah menambahkan ke rangkaian tanggal yang hilang dengan hitungan 0. Saya lebih suka memiliki grafik penuh 30 hari dengan nilai 0. Jika pendekatan ini benar, ada saran tentang bagaimana memulainya? Apakah saya membutuhkan semacam dinamikareindex fungsi ?

Berikut potongan S ( df.groupby(['simpleDate']).size() ), tidak ada entri untuk 04 dan 05.

09-02-2013     2
09-03-2013    10
09-06-2013     5
09-07-2013     1
KHibma
sumber

Jawaban:

257

Anda bisa menggunakan Series.reindex:

import pandas as pd

idx = pd.date_range('09-01-2013', '09-30-2013')

s = pd.Series({'09-02-2013': 2,
               '09-03-2013': 10,
               '09-06-2013': 5,
               '09-07-2013': 1})
s.index = pd.DatetimeIndex(s.index)

s = s.reindex(idx, fill_value=0)
print(s)

hasil

2013-09-01     0
2013-09-02     2
2013-09-03    10
2013-09-04     0
2013-09-05     0
2013-09-06     5
2013-09-07     1
2013-09-08     0
...
unutbu
sumber
23
reindexadalah fungsi yang luar biasa. Ini dapat (1) menyusun ulang data yang ada agar sesuai dengan kumpulan label baru, (2) menyisipkan baris baru yang sebelumnya tidak ada label, (3) mengisi data untuk label yang hilang, (termasuk dengan pengisian maju / mundur) (4) pilih baris dengan label!
unutbu
@unutbu Ini menjawab sebagian dari pertanyaan yang saya miliki, terima kasih! Tetapi bertanya-tanya apakah Anda tahu cara membuat daftar secara dinamis dengan tanggal yang memiliki acara?
Nick Duddy
2
Namun ada satu masalah (atau bug) dengan indeks ulang: ia tidak berfungsi dengan tanggal sebelum 1/1/1970, jadi dalam kasus ini df.resample () berfungsi dengan sempurna.
Sergey Gulbin
2
Anda dapat menggunakan ini sebagai gantinya untuk idx untuk melewati memasukkan tanggal mulai dan akhir secara manual:idx = pd.date_range(df.index.min(), df.index.max())
Reveille
Menjatuhkan tautan ke dokumentasi di sini, untuk menghemat penelusuran: pandas.pydata.org/pandas-docs/stable/reference/api/…
Harm te Molder
41

Solusi yang lebih cepat adalah dengan menggunakan .asfreq(). Ini tidak memerlukan pembuatan indeks baru untuk dipanggil .reindex().

# "broken" (staggered) dates
dates = pd.Index([pd.Timestamp('2012-05-01'), 
                  pd.Timestamp('2012-05-04'), 
                  pd.Timestamp('2012-05-06')])
s = pd.Series([1, 2, 3], dates)

print(s.asfreq('D'))
2012-05-01    1.0
2012-05-02    NaN
2012-05-03    NaN
2012-05-04    2.0
2012-05-05    NaN
2012-05-06    3.0
Freq: D, dtype: float64
Brad Solomon
sumber
1
Saya sangat menyukai metode ini; Anda menghindari panggilan date_rangekarena secara implisit menggunakan indeks pertama dan terakhir sebagai awal dan akhir (yang hampir selalu Anda inginkan).
Michael Hays
Metode yang sangat bersih dan profesional. Bekerja dengan baik dengan menggunakan interpolasi sesudahnya juga.
msarafzadeh
27

Satu masalah adalah itu reindexakan gagal jika ada nilai duplikat. Katakanlah kita sedang bekerja dengan data cap waktu, yang ingin kita indeks berdasarkan tanggal:

df = pd.DataFrame({
    'timestamps': pd.to_datetime(
        ['2016-11-15 1:00','2016-11-16 2:00','2016-11-16 3:00','2016-11-18 4:00']),
    'values':['a','b','c','d']})
df.index = pd.DatetimeIndex(df['timestamps']).floor('D')
df

hasil

            timestamps             values
2016-11-15  "2016-11-15 01:00:00"  a
2016-11-16  "2016-11-16 02:00:00"  b
2016-11-16  "2016-11-16 03:00:00"  c
2016-11-18  "2016-11-18 04:00:00"  d

Karena 2016-11-16tanggal duplikat , upaya untuk mengindeks ulang:

all_days = pd.date_range(df.index.min(), df.index.max(), freq='D')
df.reindex(all_days)

gagal dengan:

...
ValueError: cannot reindex from a duplicate axis

(dengan ini berarti indeks memiliki duplikat, bukan duplikat itu sendiri)

Sebaliknya, kita dapat menggunakan .locuntuk mencari entri untuk semua tanggal dalam kisaran:

df.loc[all_days]

hasil

            timestamps             values
2016-11-15  "2016-11-15 01:00:00"  a
2016-11-16  "2016-11-16 02:00:00"  b
2016-11-16  "2016-11-16 03:00:00"  c
2016-11-17  NaN                    NaN
2016-11-18  "2016-11-18 04:00:00"  d

fillna dapat digunakan pada rangkaian kolom untuk mengisi kekosongan jika diperlukan.

Nick Edgar
sumber
Ada ide tentang apa yang harus dilakukan jika kolom Tanggal berisi Blanksatau NULLS? df.loc[all_days]tidak akan berfungsi dalam kasus itu.
Furqan Hashim
1
Meneruskan suka daftar ke .loc atau [] dengan label yang hilang akan memunculkan KeyError di masa mendatang, Anda dapat menggunakan .reindex () sebagai alternatif. Lihat dokumentasinya di sini: pandas.pydata.org/pandas-docs/stable/…
Dmitrii Magas
19

Pendekatan alternatif adalah resample, yang dapat menangani tanggal duplikat selain tanggal yang hilang. Sebagai contoh:

df.resample('D').mean()

resampleadalah operasi yang ditangguhkan groupbysehingga Anda harus mengikutinya dengan operasi lain. Dalam hal ini meanbekerja dengan baik, tetapi Anda juga dapat menggunakan berbagai metode panda lain seperti max, sum, dll

Berikut adalah data asli, tetapi dengan entri tambahan untuk '2013-09-03':

             val
date           
2013-09-02     2
2013-09-03    10
2013-09-03    20    <- duplicate date added to OP's data
2013-09-06     5
2013-09-07     1

Dan inilah hasilnya:

             val
date            
2013-09-02   2.0
2013-09-03  15.0    <- mean of original values for 2013-09-03
2013-09-04   NaN    <- NaN b/c date not present in orig
2013-09-05   NaN    <- NaN b/c date not present in orig
2013-09-06   5.0
2013-09-07   1.0

Saya meninggalkan tanggal yang hilang sebagai NaN untuk memperjelas cara kerjanya, tetapi Anda dapat menambahkan fillna(0)untuk mengganti NaN dengan nol seperti yang diminta oleh OP atau sebagai alternatif menggunakan sesuatu seperti interpolate()mengisi dengan nilai bukan nol berdasarkan baris tetangga.

JohnE
sumber
6

Berikut adalah metode yang bagus untuk mengisi tanggal yang hilang ke dalam kerangka data, dengan pilihan Anda fill_value, days_backuntuk mengisi, dan mengurutkan urutan ( date_order) yang digunakan untuk mengurutkan kerangka data:

def fill_in_missing_dates(df, date_col_name = 'date',date_order = 'asc', fill_value = 0, days_back = 30):

    df.set_index(date_col_name,drop=True,inplace=True)
    df.index = pd.DatetimeIndex(df.index)
    d = datetime.now().date()
    d2 = d - timedelta(days = days_back)
    idx = pd.date_range(d2, d, freq = "D")
    df = df.reindex(idx,fill_value=fill_value)
    df[date_col_name] = pd.DatetimeIndex(df.index)

    return df
eiTan LaVi
sumber