panda GroupBy kolom dengan nilai NaN (hilang)


147

Saya memiliki DataFrame dengan banyak nilai yang hilang di kolom yang ingin saya kelompokkan:

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]}

melihat bahwa Panda telah menjatuhkan baris dengan nilai target NaN. (Saya ingin memasukkan baris ini!)

Karena saya memerlukan banyak operasi seperti itu (banyak cols memiliki nilai yang hilang), dan menggunakan fungsi yang lebih rumit daripada hanya median (biasanya hutan acak), saya ingin menghindari penulisan potongan kode yang terlalu rumit.

Ada saran? Haruskah saya menulis fungsi untuk ini atau apakah ada solusi sederhana?


1
@PhillipCloud Saya telah mengedit pertanyaan ini untuk memasukkan hanya pertanyaan, yang sebenarnya cukup bagus, berkaitan dengan peningkatan panda terbuka milik Jeff.
Andy Hayden

1
Tidak ada yang bisa memasukkan (dan menyebarkan) NaN dalam kelompok cukup menjengkelkan. Mengutip R tidak meyakinkan, karena perilaku ini tidak konsisten dengan banyak hal lain. Bagaimanapun, hack dummy juga sangat buruk. Namun, ukuran (termasuk NaNs) dan jumlah (mengabaikan NaNs) grup akan berbeda jika ada NaNs. dfgrouped = df.groupby (['b']). a.agg (['sum', 'size', 'count']) dfgrouped ['sum'] [dfgrouped ['size']! = dfgrouped ['count ']] = Tidak Ada
Brian Preslopsky

Bisakah Anda meringkas apa yang secara spesifik ingin Anda capai? yaitu kita melihat output, tapi apa output yang "diinginkan"?
ca

2
Dengan panda 1.1 Anda akan segera dapat menentukan dropna=Falsedalam groupby()untuk mendapatkan hasil yang Anda inginkan. Info lebih lanjut
cs95

Jawaban:


130

Ini disebutkan di bagian Data Hilang dari dokumen :

Grup NA di GroupBy secara otomatis dikecualikan. Perilaku ini konsisten dengan R, misalnya.

Salah satu solusinya adalah menggunakan placeholder sebelum melakukan groupby (misalnya -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

Yang mengatakan, ini terasa hack yang cukup mengerikan ... mungkin harus ada opsi untuk memasukkan NaN dalam groupby (lihat masalah github ini - yang menggunakan hack placeholder yang sama).


4
Ini adalah solusi logis tetapi semacam lucu yang saya pikirkan sebelumnya, Pandas membuat bidang NaN dari yang kosong, dan kita harus mengubahnya kembali. Ini adalah alasan mengapa saya berpikir untuk mencari solusi lain seperti menjalankan server SQL dan menanyakan tabel dari sana (terlihat agak terlalu rumit), atau mencari pustaka lain terlepas dari Pandas, atau menggunakan milik saya sendiri (yang saya inginkan untuk menyingkirkan). Thx
Gyula Sámuel Karli

@ GyulaSámuelKarli Bagi saya ini sepertinya bug kecil (lihat laporan bug di atas), dan solusi saya adalah solusinya. Saya merasa aneh Anda menghapus seluruh perpustakaan.
Andy Hayden

1
Saya tidak ingin menuliskan Panda hanya mencari alat yang paling sesuai dengan permintaan saya.
Gyula Sámuel Karli

1
Lihat jawaban saya di bawah ini, saya yakin saya telah menemukan solusi yang cukup bagus (bersih, dan mungkin lebih cepat). stackoverflow.com/a/43375020/408853
ca

4
Tidak, ini tidak konsisten dengan R. df%>% group_by akan memberikan ringkasan NA juga dengan peringatan yang dapat dihindari dengan melewati kolom pengelompokan melalui fct_explicit_na dan kemudian level (Hilang) dibuat.
Ravaging Care

40

Topik kuno, jika seseorang masih menemukan ini - solusi lain adalah mengonversi melalui .astype (str) ke string sebelum pengelompokan. Itu akan menghemat 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: Lihat komentar di tautan Anda - penulis pos di tautan Anda melakukan kesalahan.
Thomas

@ Thomas, ya, persis seperti pada contoh di atas. Harap edit jika Anda dapat menjadikan contoh tersebut aman (dan sepele).
K3 --- rnc

The sumdari aadalah penggabungan string di sini, bukan jumlah numerik. Ini hanya "berfungsi" karena 'b' terdiri dari entri yang berbeda. Anda perlu 'a' menjadi numerik dan 'b' menjadi string
BallpointBen

28

panda> = 1.1

Dari panda 1.1 Anda akan memiliki kontrol yang lebih baik atas perilaku ini, nilai-nilai NA sekarang diizinkan dalam kerapu menggunakan 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

Anda dapat menginstal versi pra-rilis v1.1 menggunakan perintah berikut:

pip install https://github.com/pandas-dev/pandas/releases/download/v1.1.0rc0/pandas-1.1.0rc0.tar.gz

4
Semoga jawaban ini membuat langkah bertahap ke atas. Itu pendekatan yang benar.
kdbanman

Saya rasa 1,1 belum dirilis. Diperiksa pada conda dan pip dan versi masih ada 1.0.4
sammywemmy

1
@sammywemmy Ya, untuk saat ini ini hanya dapat dijalankan di dalam lingkungan pengembangan . Saya suka mendapatkan headstart ketika datang untuk memperkenalkan fitur baru ke posting SO lama. ;-)
cs95

9

Saya tidak dapat menambahkan komentar ke M. Kiewisch karena saya tidak memiliki poin reputasi yang cukup (hanya memiliki 41 tetapi perlu lebih dari 50 untuk berkomentar).

Bagaimanapun, hanya ingin menunjukkan bahwa solusi M. Kiewisch tidak berfungsi sebagaimana mestinya dan mungkin perlu lebih banyak tweaker. Pertimbangkan misalnya

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

yang menunjukkan bahwa untuk grup b = 4.0, nilai yang sesuai adalah 15 bukannya 6. Di sini ia hanya merangkai 1 dan 5 sebagai string daripada menambahkannya sebagai angka.


12
Itu karena Anda mengonversi seluruh DF ke str, bukan hanya bkolom
Korem

Perhatikan bahwa ini telah diperbaiki pada jawaban yang disebutkan sekarang.
Shaido

1
Solusi baru lebih baik tetapi masih tidak aman, menurut saya. Pertimbangkan suatu kasus di mana salah satu entri di kolom 'b' sama dengan np.NaN yang dikurifikasi. Kemudian hal-hal itu dipukul bersama. 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

Satu poin kecil untuk solusi Andy Hayden - itu tidak berfungsi (lagi?) Karena np.nan == np.nanmenghasilkan False, sehingga replacefungsi tersebut tidak benar-benar melakukan apa-apa.

Apa yang berhasil bagi saya adalah ini:

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

(Setidaknya itulah perilaku Pandas 0.19.2. Maaf untuk menambahkannya sebagai jawaban yang berbeda, saya tidak memiliki reputasi yang cukup untuk berkomentar.)


12
Ada juga df['b'].fillna(-1).
K3 --- rnc

6

Semua jawaban yang diberikan sejauh ini menghasilkan perilaku yang berpotensi berbahaya karena sangat mungkin Anda memilih nilai dummy yang sebenarnya merupakan bagian dari dataset. Ini semakin mungkin karena Anda membuat grup dengan banyak atribut. Sederhananya, pendekatan itu tidak selalu digeneralisasi dengan baik.

Pemecahan yang kurang rumit adalah dengan menggunakan pd.drop_duplicates () untuk membuat indeks kombinasi nilai yang unik, masing-masing dengan ID mereka sendiri, dan kemudian mengelompokkan pada id itu. Itu lebih verbose tetapi menyelesaikan pekerjaan:

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]

Perhatikan bahwa Anda sekarang dapat melakukan hal berikut:

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

Ini akan mengembalikan hasil yang sukses tanpa harus khawatir menimpa data nyata yang keliru sebagai nilai dummy.


Ini adalah solusi terbaik untuk kasus umum, tetapi dalam kasus di mana saya mengetahui string / angka yang tidak valid yang dapat saya gunakan sebagai gantinya, saya mungkin akan menjawab dengan jawaban Andy Hayden di bawah ini ... Saya harap panda memperbaiki perilaku ini segera.
Sarah Messer

4

Saya sudah menjawab ini, tetapi beberapa alasan jawabannya diubah menjadi komentar. Namun demikian, ini adalah solusi paling efisien:

Tidak bisa memasukkan (dan menyebarkan) NaN dalam kelompok cukup menjengkelkan. Mengutip R tidak meyakinkan, karena perilaku ini tidak konsisten dengan banyak hal lain. Bagaimanapun, hack dummy juga sangat buruk. Namun, ukuran (termasuk NaNs) dan jumlah (mengabaikan NaNs) grup akan berbeda jika ada NaNs.

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

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

Ketika ini berbeda, Anda dapat mengatur nilai kembali ke Tidak ada untuk hasil fungsi agregasi untuk grup itu.


1
Ini sangat membantu saya tetapi menjawab pertanyaan yang sedikit berbeda dari yang asli. IIUC, solusi Anda menyebarkan NaN di penjumlahan, tetapi item NaN di kolom "b" masih bisa dijatuhkan sebagai baris.
Andrew

0

Dipasang Pandas 1.1 di Anaconda

Saya tidak dapat mengomentari jawaban cs95 tetapi dia membantu saya untuk menyelesaikan masalah.

Saya mencoba menginstal Pandas 1.1 tetapi gagal menggunakan kodenya, jadi saya googled dan dapat menginstal.

Saya pertama kali menjalankan anaconda prompt sebagai administrator dan menempelkan kode berikut:

pip install pandas==1.1.0rc0

Setelah itu termasuk penggunaan dropna = False

Tautan: https://libraries.io/pypi/panda


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.