เพิ่มวันที่ที่ขาดหายไปในกรอบข้อมูลแพนด้า


129

ข้อมูลของฉันอาจมีหลายเหตุการณ์ในวันที่กำหนดหรือไม่มีเหตุการณ์ในวันที่ ฉันใช้เหตุการณ์เหล่านี้นับตามวันที่และวางแผนไว้ อย่างไรก็ตามเมื่อฉันวางโครงเรื่องทั้งสองซีรีส์ของฉันไม่ตรงกันเสมอไป

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

ในโค้ดด้านบนidxจะกลายเป็นช่วงของ say 30 วัน 09-01-2013 ถึง 09-30-2013 อย่างไรก็ตามSอาจมีเวลาเพียง 25 หรือ 26 วันเนื่องจากไม่มีเหตุการณ์ใด ๆ เกิดขึ้นในวันที่กำหนด จากนั้นฉันจะได้รับ AssertionError เนื่องจากขนาดไม่ตรงกันเมื่อฉันพยายามพล็อต:

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

อะไรคือวิธีที่เหมาะสมในการจัดการกับปัญหานี้? ฉันต้องการลบวันที่ที่ไม่มีค่าออกจากIDXหรือ (ซึ่งฉันอยากจะทำ) คือเพิ่มวันที่ที่ขาดหายไปในชุดด้วยจำนวน 0 ฉันอยากจะมีกราฟเต็ม 30 วันที่มีค่า 0 หากแนวทางนี้ถูกต้องมีข้อเสนอแนะในการเริ่มต้นอย่างไร ฉันต้องการreindexฟังก์ชันไดนามิกบางประเภทหรือไม่?

นี่คือตัวอย่างS ( df.groupby(['simpleDate']).size() ) โปรดสังเกตว่าไม่มีรายการสำหรับ 04 และ 05

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

คำตอบ:


258

คุณสามารถใช้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)

อัตราผลตอบแทน

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
...

23
reindexเป็นฟังก์ชันที่น่าทึ่ง สามารถ (1) จัดลำดับข้อมูลที่มีอยู่ใหม่เพื่อให้ตรงกับชุดป้ายกำกับใหม่ (2) แทรกแถวใหม่ที่ไม่มีป้ายกำกับมาก่อน (3) กรอกข้อมูลสำหรับป้ายกำกับที่ขาดหายไป (รวมถึงการเติมไปข้างหน้า / ย้อนกลับ) (4) เลือกแถว ตามป้าย!
unutbu

@unutbu คำตอบนี้เป็นส่วนหนึ่งของคำถามที่ฉันมีเช่นกันขอบคุณ! แต่สงสัยว่าคุณรู้วิธีสร้างรายการแบบไดนามิกพร้อมวันที่มีเหตุการณ์หรือไม่?
Nick Duddy

2
มีปัญหาหนึ่ง (หรือข้อบกพร่อง) กับการทำดัชนีซ้ำ: มันใช้ไม่ได้กับวันที่ก่อน 1/1/1970 ดังนั้นในกรณีนี้ df.resample () จะทำงานได้อย่างสมบูรณ์
Sergey Gulbin

2
คุณสามารถใช้สิ่งนี้แทน idx เพื่อข้ามการป้อนวันที่เริ่มต้นและวันที่สิ้นสุดด้วยตนเอง:idx = pd.date_range(df.index.min(), df.index.max())
เปิดเผย

วางลิงก์ไปยังเอกสารที่นี่เพื่อบันทึกการค้นหา: pandas.pydata.org/pandas-docs/stable/reference/api/…
Harm te Molder

41

.asfreq()การแก้ปัญหาได้เร็วขึ้นคือการใช้งาน .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

1
ฉันชอบวิธีนี้มาก คุณหลีกเลี่ยงการเรียกใช้date_rangeเนื่องจากโดยปริยายใช้ดัชนีแรกและดัชนีสุดท้ายเป็นจุดเริ่มต้นและจุดสิ้นสุด (ซึ่งเป็นสิ่งที่คุณต้องการเกือบตลอดเวลา)
Michael Hays

วิธีการที่สะอาดและเป็นมืออาชีพมาก ใช้งานได้ดีกับการใช้ interpolate ในภายหลังเช่นกัน
msarafzadeh

27

ปัญหาหนึ่งคือreindexจะล้มเหลวหากมีค่าที่ซ้ำกัน สมมติว่าเรากำลังดำเนินการกับข้อมูลที่ประทับเวลาซึ่งเราต้องการจัดทำดัชนีตามวันที่:

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

อัตราผลตอบแทน

            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

เนื่องจาก2016-11-16วันที่ซ้ำกันความพยายามที่จะทำดัชนีใหม่:

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

ล้มเหลวด้วย:

...
ValueError: cannot reindex from a duplicate axis

(ด้วยเหตุนี้หมายความว่าดัชนีมีรายการที่ซ้ำกันไม่ใช่ว่าเป็นข้อมูลซ้ำซ้อน)

แต่เราสามารถใช้ .locเพื่อค้นหารายการสำหรับวันที่ทั้งหมดในช่วง:

df.loc[all_days]

อัตราผลตอบแทน

            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 สามารถใช้กับชุดคอลัมน์เพื่อเติมช่องว่างได้หากจำเป็น


มีความคิดว่าจะทำอย่างไรถ้าคอลัมน์ Date มีBlanksหรือNULLS? df.loc[all_days]จะไม่ทำงานในกรณีนั้น
Furqan Hashim

1
การส่งรายการชอบไปที่. loc หรือ [] โดยมีป้ายกำกับที่ขาดหายไปจะทำให้ KeyError เพิ่มขึ้นในอนาคตคุณสามารถใช้. reindex () เป็นทางเลือกอื่นได้ ดูเอกสารที่นี่: pandas.pydata.org/pandas-docs/stable/…
Dmitrii Magas

19

อีกทางเลือกหนึ่งคือresampleสามารถจัดการวันที่ซ้ำกันได้นอกเหนือจากวันที่ที่ขาดหายไป ตัวอย่างเช่น:

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

resampleเป็นการดำเนินการที่รอการตัดบัญชีgroupbyดังนั้นคุณต้องติดตามด้วยการดำเนินการอื่น ในกรณีนี้meanทำงานได้ดี แต่คุณยังสามารถใช้วิธีการอื่น ๆ อีกมากมายหมีแพนด้าเหมือนmax,sumฯลฯ

นี่คือข้อมูลดั้งเดิม แต่มีรายการเพิ่มเติมสำหรับ '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

และนี่คือผลลัพธ์:

             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

ฉันทิ้งวันที่ที่ขาดหายไปเป็น NaN เพื่อให้ชัดเจนว่ามันทำงานอย่างไร แต่คุณสามารถเพิ่มfillna(0)เพื่อแทนที่ NaN ด้วยศูนย์ตามที่ OP ร้องขอหรือใช้วิธีอื่นเช่นinterpolate()เติมค่าที่ไม่ใช่ศูนย์ตามแถวข้างเคียง


6

นี่เป็นวิธีที่ดีที่จะกรอกข้อมูลลงในวันที่หายไปลงใน dataframe กับตัวเลือกของคุณfill_value, days_backในการกรอกและลำดับการจัดเรียง ( date_order) โดยที่ในการจัดเรียง dataframe นี้:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.