web-dev-qa-db-ja.com

NaN(欠損)値を持つグループ化列

私はグループ化したい列に多くの欠損値を持つ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ターゲット値を持つ行をドロップしたことを確認してください。 (これらの行を含めたい!)

多くのそのような操作が必要なので(多くの列には値がありません)、中央値(通常はランダムフォレスト)よりも複雑な関数を使用するため、複雑なコードの記述を避けたいです

助言がありますか?これのために関数を書くべきですか、それとも簡単な解決策がありますか?

106

これは ドキュメントの欠落データセクションで説明

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

とはいえ、これはかなりひどいハックを感じます...おそらくNabyをgroupbyに含めるオプションがあるはずです( このgithubの問題 -同じプレースホルダーハックを使用します) )。

101
Andy Hayden

古代のトピック、誰かがまだこれにつまずく場合-別の回避策は、グループ化する前に.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
25
M. Kiewisch

M. Kiewischにコメントを追加することはできません。評判ポイントが十分にないためです(41しかありませんが、コメントするには50を超える必要があります)。

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

7

Andy Haydenのソリューションの1つの小さなポイント– np.nan == np.nanFalseを生成するため、機能しません(もう??)。したがって、replace関数は実際には何もしません。

私のために働いたのはこれでした:

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

(少なくとも、それはPandas 0.19.2の動作です。別の回答として追加してすみません、コメントするのに十分な評判がありません。)

6
Tuetschek

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

あまり面倒ではない解決策は、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')]))

これは、ダミー値と間違えられた実際のデータを上書きすることを心配することなく、成功した結果を返します。

3
Grant Langseth

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

グループにNaNを含める(および伝搬する)ことができないことは、非常に深刻です。この振る舞いは他の多くのことと矛盾するため、Rを引用することは納得できません。とにかく、ダミーのハックもかなり悪いです。ただし、NaNがある場合、グループのサイズ(NaNを含む)とカウント(NaNを無視する)は異なります。

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

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

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

2