pandas GroupBy คอลัมน์ที่มีค่า NaN (หายไป)


147

ฉันมี DataFrame ที่มีค่าหายไปจำนวนมากในคอลัมน์ที่ฉันต้องการจัดกลุ่มตาม:

import pandas as pd
import numpy as np
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})

In [4]: df.groupby('b').groups
Out[4]: {'4': [0], '6': [2]}

ดูว่า Pandas ทำดร็อปแถวด้วยค่าเป้าหมาย NaN (ฉันต้องการรวมแถวเหล่านี้ด้วย!)

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

ข้อเสนอแนะใด ๆ ฉันควรจะเขียนฟังก์ชั่นสำหรับสิ่งนี้หรือมีวิธีแก้ปัญหาง่ายๆหรือไม่?


1
@PhillipCloud ฉันได้แก้ไขคำถามนี้เพื่อรวมเพียงคำถามซึ่งเป็นจริงค่อนข้างดีที่เกี่ยวข้องกับการปรับปรุงแพนด้าเปิดของ Jeff's
Andy Hayden

1
ไม่มีความสามารถในการรวม (และเผยแพร่) NaNs ในกลุ่มค่อนข้างทำให้รุนแรงขึ้น การอ้างถึง R ไม่น่าเชื่อถือเนื่องจากพฤติกรรมนี้ไม่สอดคล้องกับสิ่งอื่น ๆ มากมาย อย่างไรก็ตามแฮ็คหลอกก็เลวร้ายเช่นกัน อย่างไรก็ตามขนาด (รวมถึง NaNs) และจำนวน (ไม่สนใจ NaNs) ของกลุ่มจะแตกต่างกันหากมี NaNs dfgrouped = df.groupby (['b']). a.agg (['ผลรวม', 'ขนาด', 'จำนวน']) dfgrouped ['ผลรวม'] [dfgrouped ['ขนาด']! = dfgrouped ['จำนวน ']] = ไม่มี
Brian Preslopsky

คุณสามารถสรุปสิ่งที่คุณพยายามทำโดยเฉพาะได้หรือไม่? เช่นเราเห็นผลลัพธ์ แต่ผลลัพธ์ "ต้องการ" คืออะไร
ca

2
ด้วยแพนด้า 1.1 คุณจะสามารถระบุdropna=Falseในไม่ช้าเพื่อgroupby()ให้ได้ผลลัพธ์ที่คุณต้องการ ข้อมูลเพิ่มเติม
cs95

คำตอบ:


130

สิ่งนี้ถูกกล่าวถึงในส่วน Missing Data ของเอกสาร :

กลุ่ม NA ใน GroupBy จะถูกแยกออกโดยอัตโนมัติ พฤติกรรมนี้สอดคล้องกับ R ตัวอย่างเช่น

วิธีแก้ปัญหาหนึ่งคือการใช้ตัวยึดตำแหน่งก่อนที่จะทำ groupby (เช่น -1):

In [11]: df.fillna(-1)
Out[11]: 
   a   b
0  1   4
1  2  -1
2  3   6

In [12]: df.fillna(-1).groupby('b').sum()
Out[12]: 
    a
b    
-1  2
4   1
6   3

ที่กล่าวมานี้รู้สึกแฮ็คที่แย่มาก ... บางทีอาจมีตัวเลือกให้รวม NaN ในกลุ่มโดยดูที่ปัญหา GitHubซึ่งใช้แฮ็คตำแหน่งเดิมแทน


4
นี่เป็นตรรกะ แต่เป็นวิธีแก้ปัญหาตลก ๆ ที่ฉันคิดไว้ก่อนหน้านี้ Pandas สร้างฟิลด์ NaN จากฟิลด์ว่างและเราต้องเปลี่ยนมันกลับ นี่คือเหตุผลที่ฉันคิดว่ากำลังมองหาวิธีแก้ปัญหาอื่น ๆ เช่นการเรียกใช้เซิร์ฟเวอร์ SQL และค้นหาตารางจากที่นั่น (ดูซับซ้อนเกินไป) หรือมองหาห้องสมุดอื่นทั้งๆที่ Pandas หรือใช้ของตัวเอง (ที่ฉันต้องการ เพื่อกำจัด). ขอบคุณ
Gyula Sámuel Karli

@ GyulaSámuelKarliสำหรับฉันนี่ดูเหมือนข้อผิดพลาดเล็ก ๆ (ดู bugreport ด้านบน) และวิธีแก้ปัญหาของฉันคือวิธีแก้ปัญหา ฉันคิดว่ามันแปลกที่คุณตัดห้องสมุดทั้งหมด
Andy Hayden

1
ฉันไม่ต้องการที่จะเขียน Pandas เพียงแค่มองหาเครื่องมือที่เหมาะกับคำขอของฉันมากที่สุด
Gyula Sámuel Karli

1
ดูคำตอบของฉันด้านล่างนี้ฉันเชื่อว่าฉันพบวิธีแก้ปัญหาที่ดี (สะอาดกว่าและเร็วกว่า) stackoverflow.com/a/43375020/408853
ca

4
ไม่สิ่งนี้ไม่สอดคล้องกับ R. df%>% group_by จะให้บทสรุปของ NA ด้วยเช่นกันซึ่งสามารถหลีกเลี่ยงได้โดยผ่านคอลัมน์การจัดกลุ่มผ่าน fct_explicit_na แล้วสร้างระดับ (ที่หายไป)
Ravaging Care

40

หัวข้อโบราณถ้ามีคนยังคงสะดุดกับเรื่องนี้ - วิธีแก้ปัญหาอื่นคือการแปลงผ่าน .astype (str) เป็นสตริงก่อนจัดกลุ่ม ที่จะอนุรักษ์ NaN ของ

in:
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})
df['b'] = df['b'].astype(str)
df.groupby(['b']).sum()
out:
    a
b   
4   1
6   3
nan 2

@ K3 --- rnc: ดูความคิดเห็นที่ลิงค์ของคุณ - ผู้โพสต์ในลิงค์ของคุณทำอะไรผิด
โทมัส

@ โทมัสใช่ตรงตามตัวอย่างด้านบน โปรดแก้ไขหากคุณสามารถทำให้ตัวอย่างปลอดภัย (และไม่สำคัญ)
K3 --- rnc

sumของaเป็นสตริงที่นี่ไม่ได้เป็นผลรวมตัวเลข นี่เป็นเพียง "งาน" เพราะ 'b' ประกอบด้วยรายการที่แตกต่าง คุณต้อง 'a' จะเป็นตัวเลขและ b จะเป็นสตริง
BallpointBen

28

แพนด้า> = 1.1

จาก pandas 1.1 คุณสามารถควบคุมพฤติกรรมนี้ได้ดีขึ้นค่า NA ได้รับอนุญาตให้ใช้ในปลาเก๋าโดยใช้dropna=False:

pd.__version__
# '1.1.0.dev0+2004.g8d10bfb6f'

# Example from the docs
df

   a    b  c
0  1  2.0  3
1  1  NaN  4
2  2  1.0  3
3  1  2.0  2

# without NA (the default)
df.groupby('b').sum()

     a  c
b        
1.0  2  3
2.0  2  5
# with NA
df.groupby('b', dropna=False).sum()

     a  c
b        
1.0  2  3
2.0  2  5
NaN  1  4

4
หวังว่าคำตอบนี้จะค่อย ๆ เดินขึ้นไปด้านบน มันเป็นวิธีการที่ถูกต้อง
kdbanman

ฉันคิดว่า 1.1 ยังไม่ได้เปิดตัว ตรวจสอบ conda และ pip ​​และรุ่นยังมี 1.0.4
sammywemmy

1
@sammywemmy ใช่แล้วสำหรับตอนนี้สามารถเรียกใช้ภายในสภาพแวดล้อมการพัฒนาเท่านั้น ฉันชอบที่จะเริ่มต้นเมื่อมันมาถึงการแนะนำคุณสมบัติใหม่เพื่อโพสต์ SO เก่า ;-)
cs95

9

ฉันไม่สามารถเพิ่มความคิดเห็นใน M. Kiewisch เนื่องจากฉันมีคะแนนชื่อเสียงไม่เพียงพอ (มี 41 แต่ต้องการมากกว่า 50 ความคิดเห็น)

อย่างไรก็ตามเพียงแค่ต้องการชี้ให้เห็นว่าวิธีการแก้ปัญหาเอ็ม Kiewisch ไม่ทำงานตามที่เป็นอยู่และอาจต้องปรับแต่งเพิ่มเติม ลองพิจารณาตัวอย่าง

>>> df = pd.DataFrame({'a': [1, 2, 3, 5], 'b': [4, np.NaN, 6, 4]})
>>> df
   a    b
0  1  4.0
1  2  NaN
2  3  6.0
3  5  4.0
>>> df.groupby(['b']).sum()
     a
b
4.0  6
6.0  3
>>> df.astype(str).groupby(['b']).sum()
      a
b
4.0  15
6.0   3
nan   2

ซึ่งแสดงให้เห็นว่าสำหรับกลุ่ม b = 4.0 ค่าที่สอดคล้องกันคือ 15 แทน 6 ที่นี่มันเป็นเพียงแค่การเชื่อม 1 และ 5 เป็นสตริงแทนการเพิ่มเป็นตัวเลข


12
นั่นเป็นเพราะคุณแปลง DF ทั้งหมดเป็น str แทนที่จะเป็นbคอลัมน์
Korem

โปรดทราบว่านี่ได้รับการแก้ไขแล้วในคำตอบที่กล่าวถึงในขณะนี้
Shaido - Reinstate Monica

1
ทางออกใหม่ดีกว่า แต่ก็ยังไม่ปลอดภัยในความคิดของฉัน พิจารณากรณีที่หนึ่งในรายการในคอลัมน์ 'b' เหมือนกับ stringified np.NaN จากนั้นสิ่งเหล่านั้นจะพานกัน df = pd.DataFrame ({'a': [1, 2, 3, 5, 6], 'b': ['foo', np.NaN, 'bar', 'foo', 'nan']}) ; df ['b'] = df ['b']. astype (str); df.groupby (['b']). sum ()
Kamaraju Kusumanchi

6

จุดเล็ก ๆ จุดหนึ่งสำหรับการแก้ปัญหาของ Andy Hayden - มันไม่ทำงาน (อีกต่อไป?) เพราะnp.nan == np.nanให้ผลFalseดังนั้นreplaceฟังก์ชั่นไม่ได้ทำอะไรเลย

สิ่งที่ใช้ได้ผลสำหรับฉันคือ:

df['b'] = df['b'].apply(lambda x: x if not np.isnan(x) else -1)

(อย่างน้อยนั่นคือพฤติกรรมของ Pandas 0.19.2. ขออภัยที่จะเพิ่มเป็นคำตอบที่ต่างออกไปฉันไม่มีชื่อเสียงพอที่จะแสดงความคิดเห็น)


12
df['b'].fillna(-1)นอกจากนี้ยังมี
K3 --- rnc

6

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

วิธีแก้ปัญหาแฮ็คที่น้อยกว่าคือการใช้ pd.drop_duplicates () เพื่อสร้างดัชนีที่ไม่ซ้ำกันของการรวมค่าแต่ละค่าด้วย ID ของตัวเองแล้วจัดกลุ่มในรหัสนั้น มันละเอียดมากขึ้น แต่ทำงานให้เสร็จ:

def safe_groupby(df, group_cols, agg_dict):
    # set name of group col to unique value
    group_id = 'group_id'
    while group_id in df.columns:
        group_id += 'x'
    # get final order of columns
    agg_col_order = (group_cols + list(agg_dict.keys()))
    # create unique index of grouped values
    group_idx = df[group_cols].drop_duplicates()
    group_idx[group_id] = np.arange(group_idx.shape[0])
    # merge unique index on dataframe
    df = df.merge(group_idx, on=group_cols)
    # group dataframe on group id and aggregate values
    df_agg = df.groupby(group_id, as_index=True)\
               .agg(agg_dict)
    # merge grouped value index to results of aggregation
    df_agg = group_idx.set_index(group_id).join(df_agg)
    # rename index
    df_agg.index.name = None
    # return reordered columns
    return df_agg[agg_col_order]

โปรดทราบว่าคุณสามารถทำสิ่งต่อไปนี้ได้ทันที:

data_block = [np.tile([None, 'A'], 3),
              np.repeat(['B', 'C'], 3),
              [1] * (2 * 3)]

col_names = ['col_a', 'col_b', 'value']

test_df = pd.DataFrame(data_block, index=col_names).T

grouped_df = safe_groupby(test_df, ['col_a', 'col_b'],
                          OrderedDict([('value', 'sum')]))

สิ่งนี้จะส่งคืนผลลัพธ์ที่สำเร็จโดยไม่ต้องกังวลเกี่ยวกับการเขียนทับข้อมูลจริงที่เข้าใจผิดว่าเป็นค่าตัวอย่าง


นี่เป็นทางออกที่ดีที่สุดสำหรับกรณีทั่วไป แต่ในกรณีที่ฉันรู้ว่ามีสตริง / หมายเลขที่ไม่ถูกต้องที่ฉันสามารถใช้แทนฉันอาจจะไปกับคำตอบของ Andy Hayden ด้านล่าง ... ฉันหวังว่าแพนด้าจะแก้ไขพฤติกรรมนี้ในไม่ช้า
Sarah Messer

4

ฉันตอบไปแล้ว แต่ด้วยเหตุผลบางอย่างคำตอบก็เปลี่ยนเป็นความคิดเห็น อย่างไรก็ตามนี่เป็นวิธีที่มีประสิทธิภาพที่สุด:

การไม่สามารถรวม (และเผยแพร่) NaNs ในกลุ่มค่อนข้างทำให้รุนแรงขึ้น การอ้างถึง R ไม่น่าเชื่อถือเนื่องจากพฤติกรรมนี้ไม่สอดคล้องกับสิ่งอื่น ๆ มากมาย อย่างไรก็ตามแฮ็คหลอกก็เลวร้ายเช่นกัน อย่างไรก็ตามขนาด (รวมถึง NaNs) และจำนวน (ไม่สนใจ NaNs) ของกลุ่มจะแตกต่างกันหากมี NaNs

dfgrouped = df.groupby(['b']).a.agg(['sum','size','count'])

dfgrouped['sum'][dfgrouped['size']!=dfgrouped['count']] = None

เมื่อสิ่งเหล่านี้แตกต่างกันคุณสามารถตั้งค่ากลับเป็นไม่มีสำหรับผลลัพธ์ของฟังก์ชันการรวมสำหรับกลุ่มนั้น


1
นี่เป็นประโยชน์กับฉันมาก แต่มันก็ตอบคำถามที่แตกต่างไปจากเดิมเล็กน้อย IIUC โซลูชันของคุณเผยแพร่ NaNs ในการรวม แต่รายการ NaN ในคอลัมน์ "b" ยังคงลดลงเป็นแถว
Andrew

0

ติดตั้ง Pandas 1.1 ใน Anaconda แล้ว

ฉันไม่สามารถแสดงความคิดเห็นกับคำตอบของ cs95 ได้ แต่เขาช่วยฉันแก้ไขปัญหา

ฉันพยายามติดตั้ง Pandas 1.1 แต่มันล้มเหลวในการใช้รหัสของเขาฉันจึง googled และสามารถติดตั้งได้

ฉันเรียกใช้พรอมต์แอนาคอนดาก่อนในฐานะผู้ดูแลระบบและวางรหัสต่อไปนี้:

pip install pandas==1.1.0rc0

หลังจากนั้นรวมถึงการใช้งาน dropna = False

ลิงก์: https : //l ไลบรารี.io/pypi/pandas


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