web-dev-qa-db-ja.com

Pandas冗長nanのカテゴリを持つgroupby

pandas groupby をカテゴリデータで使用すると問題が発生します。理論的には、非常に効率的である必要があります。文字列ではなく整数を使用してグループ化し、インデックスを作成します。ただし、複数のカテゴリでグループ化する場合、カテゴリのすべての組み合わせを考慮する必要があると主張しています。

一般的な文字列の密度が低い場合でも、それらの文字列が長く、メモリを節約/パフォーマンスを向上させるという理由だけでカテゴリを使用することがあります。各列に数千のカテゴリがある場合があります。 3列でグループ化する場合、pandasは1000 ^ 3グループの結果を保持するように強制します。

私の質問:この厄介な振る舞いを避けながら、カテゴリでgroupbyを使用する便利な方法はありますか?私はこれらのソリューションを探していません。

  • numpyを介してすべての機能を再作成します。
  • groupbyの前に継続的に文字列/コードに変換し、後でカテゴリに戻します。
  • グループ列からタプル列を作成し、タプル列でグループ化します。

この特定のpandas特異性だけを修正する方法があることを望んでいます。簡単な例を以下に示します。出力に必要な4つのカテゴリの代わりに、12になります。

import pandas as pd

group_cols = ['Group1', 'Group2', 'Group3']

df = pd.DataFrame([['A', 'B', 'C', 54.34],
                   ['A', 'B', 'D', 61.34],
                   ['B', 'A', 'C', 514.5],
                   ['B', 'A', 'A', 765.4],
                   ['A', 'B', 'D', 765.4]],
                  columns=(group_cols+['Value']))

for col in group_cols:
    df[col] = df[col].astype('category')

df.groupby(group_cols, as_index=False).sum()

Group1  Group2  Group3  Value
#   A   A   A   NaN
#   A   A   C   NaN
#   A   A   D   NaN
#   A   B   A   NaN
#   A   B   C   54.34
#   A   B   D   826.74
#   B   A   A   765.40
#   B   A   C   514.50
#   B   A   D   NaN
#   B   B   A   NaN
#   B   B   C   NaN
#   B   B   D   NaN

バウンティアップデート

この問題はpandas開発チーム(cf github.com/pandas-dev/pandas/issues/17594 を参照)では不十分に対処されています。したがって、私は次のような回答を探しています。次のいずれかに対処します。

  1. pandasソースコードを参照して、groupby操作でカテゴリデータが異なる方法で処理されるのはなぜですか?
  2. なぜ現在の実装が優先されるのでしょうか?これは主観的であることに感謝していますが、この質問に対する答えを見つけるのに苦労しています。現在の動作は、面倒で潜在的に高価な回避策なしでは多くの状況で禁止されています。
  3. pandas groupby操作でのカテゴリデータの処理をオーバーライドするクリーンなソリューションはありますか?3つのノーゴールート(numpyへのドロップダウン、コードとの変換、タプル列による作成とグループ化)に注意してください)。他のpandasカテゴリ機能の損失を最小化/回避するために、 "pandas準拠"のソリューションを好むでしょう。
  4. pandas開発チームからの応答。既存の処理をサポートおよび明確化します。また、すべてのカテゴリの組み合わせをブール値パラメーターとして構成できないと考えるのはなぜですか?

バウンティアップデート#2

明確にするために、私は上記の4つの質問すべてに対する答えを期待していません。私が尋ねている主な質問は、pandasライブラリメソッドを上書きすることが可能かどうか、またはカテゴリをgroupby/set_index 操作。

18
jpp

Pandas 0.23.0)であるため、 groupbyメソッド は、パラメータobservedを取得できるようになりました。 True(デフォルトはFalse)以下は、observed=Trueだけが追加された質問とまったく同じコードです。

import pandas as pd

group_cols = ['Group1', 'Group2', 'Group3']

df = pd.DataFrame([['A', 'B', 'C', 54.34],
                   ['A', 'B', 'D', 61.34],
                   ['B', 'A', 'C', 514.5],
                   ['B', 'A', 'A', 765.4],
                   ['A', 'B', 'D', 765.4]],
                  columns=(group_cols+['Value']))

for col in group_cols:
    df[col] = df[col].astype('category')

df.groupby(group_cols, as_index=False, observed=True).sum()

enter image description here

11
Ismael EL ATIFI

Categorical Data の操作セクションに記載されている動作と同様の動作が見つかりました。

特に、

In [121]: cats2 = pd.Categorical(["a","a","b","b"], categories=["a","b","c"])

In [122]: df2 = pd.DataFrame({"cats":cats2,"B":["c","d","c","d"], "values":[1,2,3,4]})

In [123]: df2.groupby(["cats","B"]).mean()
Out[123]: 
        values
cats B        
a    c     1.0
     d     2.0
b    c     3.0
     d     4.0
c    c     NaN
     d     NaN

Seriesおよびgroupbyの関連する動作を説明する他の言葉。セクションの最後にピボットテーブルの例もあります。

Series.min()、Series.max()、およびSeries.mode()の他に、カテゴリデータでは次の操作が可能です。

Series.value_counts()などのシリーズメソッドは、一部のカテゴリがデータに存在しない場合でも、すべてのカテゴリを使用します。

Groupbyは、「未使用」カテゴリも表示します。

単語と例は Categorical Data から引用されています。

4
Tai

本当にうまくいくはずのソリューションを手に入れることができました。より良い説明で投稿を編集します。しかし、その間に、これはあなたにとってうまく機能しますか?

import pandas as pd

group_cols = ['Group1', 'Group2', 'Group3']

df = pd.DataFrame([['A', 'B', 'C', 54.34],
                   ['A', 'B', 'D', 61.34],
                   ['B', 'A', 'C', 514.5],
                   ['B', 'A', 'A', 765.4],
                   ['A', 'B', 'D', 765.4]],
                  columns=(group_cols+['Value']))
for col in group_cols:
    df[col] = df[col].astype('category')


result = df.groupby([df[col].values.codes for col in group_cols]).sum()
result = result.reset_index()
level_to_column_name = {f"level_{i}":col for i,col in enumerate(group_cols)}
result = result.rename(columns=level_to_column_name)
for col in group_cols:
    result[col] = pd.Categorical.from_codes(result[col].values, categories=df[col].values.categories)
result

したがって、この答えは、通常のPandasの質問よりも適切なプログラミングのように感じました。すべてのカテゴリシリーズは、カテゴリの名前にインデックスを付ける一連の数字に過ぎません。これらの基本的な数値は、カテゴリ列と同じ問題がないため、groupbyを使用しました。これを行った後、列の名前を変更する必要がありました。

Group1  Group2  Group3  Value
A       B       C       54.34
A       B       D       826.74
B       A       A       765.40
B       A       C       514.50

したがって、これはあなたの答えではないことを理解していますが、将来この問題を抱えている人々のために私のソリューションを小さな機能にしています。

def categorical_groupby(df,group_cols,agg_fuction="sum"):
    "Does a groupby on a number of categorical columns"
    result = df.groupby([df[col].values.codes for col in group_cols]).agg(agg_fuction)
    result = result.reset_index()
    level_to_column_name = {f"level_{i}":col for i,col in enumerate(group_cols)}
    result = result.rename(columns=level_to_column_name)
    for col in group_cols:
        result[col] = pd.Categorical.from_codes(result[col].values, categories=df[col].values.categories)
    return result

次のように呼び出します:

df.pipe(categorical_groupby,group_cols)
4
Gabriel A

ここで答えられる質問のlotがあります。
「カテゴリ」とは何かを理解することから始めましょう...

カテゴリーdtypeの定義

「カテゴリーデータ」の pandas docs から引用:

カテゴリは、pandasデータタイプ、であり、統計のカテゴリ変数に対応しています:変数。限られた、通常は固定された、可能な値の数(category; Rのレベルのみ) )。例は、性別、社会階級、血液型、所属国、観察時間、リッカート尺度による評価です。

ここで注目したい2つのポイントがあります。

  1. 統計変数としてのカテゴリーの定義:
    基本的に、これは「通常の」プログラミングではなく、統計的な観点からそれらを見る必要があることを意味します。つまり、「列挙型」ではありません。統計カテゴリ変数には特定の操作とユースケースがあります。詳細については、 wikipedia を参照してください。
    これについては、2番目のポイントの後で詳しく説明します。

  2. カテゴリはRのレベルです:
    Rのレベルと要因について読むと、カテゴリに関する詳細を理解できます。
    Rについてはあまり知りませんが、 this source シンプルで十分であることがわかりました。それから興味深い例を引用します:

    When a factor is first created, all of its levels are stored along with the factor, and if subsets of the factor are extracted, they will retain all of the original levels. This can create problems when constructing model matrices and may or may not be useful when displaying the data using, say, the table function. As an example, consider a random sample from the letters vector, which is part of the base R distribution.
    
    > lets = sample(letters,size=100,replace=TRUE)
    > lets = factor(lets)
    > table(lets[1:5])
    
    a b c d e f g h i j k l m n o p q r s t u v w x y z
    1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1
    
    Even though only five of the levels were actually represented, the table function shows the frequencies for all of the levels of the original factors. To change this, we can simply use another call to factor
    
    > table(factor(lets[1:5]))
    
    a k q s z
    1 1 1 1 1
    

基本的に、これは、不要な場合でもすべてのカテゴリを表示/使用することは珍しくありません。そして実際、それはデフォルトの動作です!
これは、統計におけるカテゴリ変数の通常のユースケースによるものです。ほとんどすべての場合、あなたはdo使用されていなくてもすべてのカテゴリを気にします。たとえば、pandas function cut を使用します。

この時点で、この動作がパンダに存在する理由を理解したことを願っています。

カテゴリー変数のGroupBy

groupbyがカテゴリのすべての組み合わせを考慮する理由:確かに言うことはできませんが、ソースコード(およびあなたが言及したgithubの問題)の簡単なレビューに基づく私の最善の推測は、カテゴリ変数のgroupbyとそれらの間の 相互作用 を考慮してください。したがって、すべてのペア/タプル(デカルト積など)を考慮する必要があります。知る限り、これは [〜#〜] anova [〜#〜] のようなことをしようとしているときに非常に役立ちます。
これは、このコンテキストでは、通常のSQLのような用語では考えられないことも意味します。

ソリューション?

わかりましたが、この動作が望ましくない場合はどうでしょうか?
私の知る限りでは、昨夜、これをpandasソースコードでトレースするのに費やしたことを考慮に入れて、「無効化」することはできません。すべての重要なステップで。
ただし、groupbyの動作方法により、実際の「拡張」は必要になるまで行われません。たとえば、グループに対してsumを呼び出すとき、またはそれらを印刷しようとするとき。
したがって、必要なグループのみを取得するには、次のいずれかを実行できます。

df.groupby(group_cols).indices
#{('A', 'B', 'C'): array([0]),
# ('A', 'B', 'D'): array([1, 4]),
# ('B', 'A', 'A'): array([3]),
# ('B', 'A', 'C'): array([2])}

df.groupby(group_cols).groups
#{('A', 'B', 'C'): Int64Index([0], dtype='int64'),
# ('A', 'B', 'D'): Int64Index([1, 4], dtype='int64'),
# ('B', 'A', 'A'): Int64Index([3], dtype='int64'),
# ('B', 'A', 'C'): Int64Index([2], dtype='int64')}

# an example
for g in df.groupby(group_cols).groups:
    print(g, grt.get_group(g).sum()[0])
#('A', 'B', 'C') 54.34
#('A', 'B', 'D') 826.74
#('B', 'A', 'A') 765.4
#('B', 'A', 'C') 514.5

私はこれがあなたのためのノーゴーであることを知っています、しかし、私はこれをする直接の方法がないことを99%確信しています。
この動作を無効にし、「通常の」SQLに似たものを使用するにはブール変数が必要であることに同意します。

3
Qusai Alothman

同様のデバッグ中にこの投稿を見つけました。非常に良い投稿であり、境界条件を含めることが本当に好きです!

最初の目標を達成するコードは次のとおりです。

r = df.groupby(group_cols, as_index=False).agg({'Value': 'sum'})

r.columns = ['_'.join(col).strip('_') for col in r.columns]

enter image description here

このソリューションの欠点は、階層型の列インデックスが作成されることです(特に複数の統計情報がある場合)。上記のコードに列インデックスのフラット化を含めました。

インスタンスメソッドがなぜなのかわかりません:

df.groupby(group_cols).sum() 
df.groupby(group_cols).mean()
df.groupby(group_cols).stdev()

.agg()メソッドでは、カテゴリ変数のすべての一意の組み合わせを使用します。

df.groupby(group_cols).agg(['count', 'sum', 'mean', 'std']) 

グループの未使用レベルの組み合わせを無視します。それは一貫していないようです。 .agg()メソッドを使用でき、デカルトの組み合わせの爆発を心配する必要がないことを嬉しく思います。

また、デカルト積よりもはるかに低い固有のカーディナリティーカウントを持つことは非常に一般的だと思います。データに「State」、「County」、「Zip」などの列があるすべてのケースを考えてください。これらはすべてネストされた変数であり、多くのデータセットには高度なネストを持つ変数があります。

この場合、グループ化変数のデカルト積と自然発生的な組み合わせの差は1000xを超えています(開始データセットは1,000,000行を超えています)。

その結果、observed = Trueをデフォルトの動作にすることに投票したでしょう。

0
Randall Goodwin