NaN(欠損)値を持つパンダGroupBy列


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のオープンパンダの拡張に関する質問だけを含めました。
アンディヘイデン

1
NaNをグループに含める(および伝播する)ことができないことは、かなり悪化します。この動作は他の多くの事柄と一致しないため、Rを引用しても説得力はありません。とにかく、ダミーのハックもかなり悪いです。ただし、NaNがある場合、グループのサイズ(NaNを含む)とカウント(NaNを無視)は異なります。dfgrouped = df.groupby(['b'])。a.agg(['sum'、 'size'、 'count'])dfgrouped ['sum'] [dfgrouped ['size']!= dfgrouped ['count ']] =なし
ブライアンプレスロプスキー2017

具体的に達成しようとしていることを要約できますか?つまり、出力が表示されますが、「望ましい」出力とは何ですか。
2017

2
pandas 1.1では、すぐに指定dropna=Falsegroupby()て希望する結果を得ることができます。詳細
cs95

回答:


130

これはドキュメントの欠落データセクションで言及されています:

GroupByのNAグループは自動的に除外されます。この動作は、たとえばRと一貫しています。

回避策の1つは、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をgroupbyに含めるオプションがあるはずです(このgithubの問題 -同じプレースホルダーのハックを使用します)。


4
これは論理的ですが、私が以前に考えた面白い解決策のようなものです。パンダスは空のフィールドからNaNフィールドを作成し、それらを元に戻す必要があります。これが、SQLサーバーを実行してそこからテーブルにクエリを実行する(少し複雑すぎるように見える)か、Pandasにもかかわらず別のライブラリを探す、または自分で使用する(私が欲しい)などの他のソリューションを探している理由です。取り除くために)。Thx
ジュラサミュエルカーリ2013

@GyulaSámuelKarli私にはこれは小さなバグのように見え(上記のバグレポートを参照)、私の解決策は回避策です。ライブラリ全体をオフに書いているのは不思議です。
アンディヘイデン

1
Pandasを書き留めたくないので、自分の要求に最も適合するツールを探します。
ジュラサミュエル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:リンクへのコメントを参照してください-リンクの投稿の作成者が何か問題を起こしました。
トーマス

@Thomas、はい、上記の例とまったく同じです。例を安全に(そして簡単に)できる場合は編集してください。
K3 --- 2018

sumのは、aここで文字列の連結ではなく、数値の合計です。「b」は異なるエントリで構成されているため、これは「機能する」だけです。「a」は数値、「b」は文字列にする必要があります
BallpointBen

28

パンダ> = 1.1

パンダ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

次のコマンドを使用して、v1.1のプレリリースバージョンをインストールできます。

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

4
うまくいけば、この答えが徐々に上へと進んでいきます。それが正しいアプローチです。
kdbanman

1.1はまだリリースされていないと思います。condaとpip、およびバージョンがまだ1.0.4であることを確認
sammywemmy

1
@sammywemmyはい、現時点では、これは開発環境内でのみ実行できます。古いSOの投稿に新しい機能を導入することに関しては、すぐに始めたいと思います。;-)
cs95

9

評判ポイントが足りないため、M。キーウィッシュにコメントを追加できません(41しかありませんが、コメントするには50以上必要です)。

とにかく、M。キーウィッシュソリューションはそのままでは機能せず、さらに調整が必要になる可能性があることを指摘しておきます。例を考えます

>>> 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の場合、対応する値が6ではなく15であることを示しています。ここでは、数値として追加するのではなく、1と5を文字列として連結しています。


12
あなただけではなく、の、STRに全体DFを変換しているためですbコラム
Korem

これは上記の回答で修正されていることに注意してください。
Shaido-モニカを復活させる

1
私の意見では、新しいソリューションの方が優れていますが、まだ安全ではありません。列「b」のエントリの1つが文字列化された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()
カマラジュクスマンチ

6

Andy Haydenの解決策の1つの小さなポイント–は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 --- 2017

6

これまでに提供されたすべての回答は、実際にデータセットの一部であるダミー値を選択する可能性があるため、潜在的に危険な動作になります。これは、多くの属性を持つグループを作成するにつれてますます可能性があります。簡単に言えば、このアプローチは常に一般化するとは限りません。

ハックが少ない解決策は、pd.drop_duplicates()を使用して、それぞれ独自のIDを持つ値の組み合わせの一意のインデックスを作成し、その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の答えに行くつもりです...パンダがすぐにこの動作を修正するといいですね。
サラメッサー

4

私はすでにこれに答えましたが、なぜか答えがコメントに変換されました。それにもかかわらず、これは最も効率的なソリューションです。

NaNをグループに含める(および伝播する)ことができないことは、かなり悪化します。この動作は他の多くの事柄と一致しないため、Rを引用しても説得力はありません。とにかく、ダミーのハックもかなり悪いです。ただし、NaNがある場合、グループのサイズ(NaNを含む)とカウント(NaNを無視)は異なります。

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

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

これらが異なる場合は、そのグループの集計関数の結果の値を[なし]に戻すことができます。


1
これは非常に役に立ちましたが、元の質問とは少し異なる質問に答えます。IIUC、ソリューションは合計でNaNを伝播しますが、「b」列のNaNアイテムは引き続き行としてドロップされます。
Andrew

0

AnacondaにPandas 1.1をインストールしました

私はcs95の答えにコメントすることはできませんが、彼は問題を解決するのを助けてくれました。

Pandas 1.1をインストールしようとしましたが、彼のコードを使用して失敗したため、グーグルでインストールできました。

最初に管理者としてanacondaプロンプトを実行し、次のコードを貼り付けます。

pip install pandas==1.1.0rc0

その後使用を含みます dropna = False

リンク:https : //libraries.io/pypi/pandas

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.