3つの文字列列を持つデータフレームがあります。 3番目の列の1つの値のみが、最初の2つのすべての組み合わせに対して有効であることを知っています。データを消去するには、最初の2列でデータフレームごとにグループ化し、各組み合わせで3列目の最も一般的な値を選択する必要があります。
私のコード:
import pandas as pd
from scipy import stats
source = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'],
'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
'Short name' : ['NY','New','Spb','NY']})
print source.groupby(['Country','City']).agg(lambda x: stats.mode(x['Short name'])[0])
コードの最後の行は機能せず、「Key error 'Short name'」と表示されます。Cityのみでグループ化しようとすると、AssertionErrorが発生します。何を修正できますか?
value_counts()
を使用してカウント系列を取得し、最初の行を取得できます。
import pandas as pd
source = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'],
'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
'Short name' : ['NY','New','Spb','NY']})
source.groupby(['Country','City']).agg(lambda x:x.value_counts().index[0])
pd.Series.mode
が利用可能です。groupby
、 GroupBy.agg
を使用し、各グループに pd.Series.mode
関数を適用します。
source.groupby(['Country','City'])['Short name'].agg(pd.Series.mode)
Country City
Russia Sankt-Petersburg Spb
USA New-York NY
Name: Short name, dtype: object
これがDataFrameとして必要な場合は、使用します
source.groupby(['Country','City'])['Short name'].agg(pd.Series.mode).to_frame()
Short name
Country City
Russia Sankt-Petersburg Spb
USA New-York NY
Series.mode
の便利な点は、特にGroupby出力を再構築する場合に、シリーズを常に返し、agg
およびapply
と非常に互換性があることです。また、高速です。
# Accepted answer.
%timeit source.groupby(['Country','City']).agg(lambda x:x.value_counts().index[0])
# Proposed in this post.
%timeit source.groupby(['Country','City'])['Short name'].agg(pd.Series.mode)
5.56 ms ± 343 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.76 ms ± 387 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Series.mode
は、multipleモードがある場合にも適切に機能します。
source2 = source.append(
pd.Series({'Country': 'USA', 'City': 'New-York', 'Short name': 'New'}),
ignore_index=True)
# Now `source2` has two modes for the
# ("USA", "New-York") group, they are "NY" and "New".
source2
Country City Short name
0 USA New-York NY
1 USA New-York New
2 Russia Sankt-Petersburg Spb
3 USA New-York NY
4 USA New-York New
source2.groupby(['Country','City'])['Short name'].agg(pd.Series.mode)
Country City
Russia Sankt-Petersburg Spb
USA New-York [NY, New]
Name: Short name, dtype: object
または、モードごとに個別の行が必要な場合は、 GroupBy.apply
を使用できます。
source2.groupby(['Country','City'])['Short name'].apply(pd.Series.mode)
Country City
Russia Sankt-Petersburg 0 Spb
USA New-York 0 NY
1 New
Name: Short name, dtype: object
それらのいずれかである限り、どのモードが返されるかdo n't careの場合、mode
を呼び出すラムダが必要になります。そして最初の結果を抽出します。
source2.groupby(['Country','City'])['Short name'].agg(
lambda x: pd.Series.mode(x)[0])
Country City
Russia Sankt-Petersburg Spb
USA New-York NY
Name: Short name, dtype: object
Pythonから statistics.mode
を使用することもできますが、...
source.groupby(['Country','City'])['Short name'].apply(statistics.mode)
Country City
Russia Sankt-Petersburg Spb
USA New-York NY
Name: Short name, dtype: object
...複数のモードを処理する必要がある場合、うまく機能しません。 StatisticsError
が発生します。これはドキュメントに記載されています:
データが空の場合、または最も一般的な値が1つだけ存在しない場合、StatisticsErrorが発生します。
しかし、あなたは自分で見ることができます...
statistics.mode([1, 2])
# ---------------------------------------------------------------------------
# StatisticsError Traceback (most recent call last)
# ...
# StatisticsError: no unique mode; found 2 equally common values
agg
の場合、lambba関数はSeries
を取得しますが、'Short name'
属性はありません。
stats.mode
は2つの配列のタプルを返すため、このタプルの最初の配列の最初の要素を取得する必要があります。
次の2つの簡単な変更により:
source.groupby(['Country','City']).agg(lambda x: stats.mode(x)[0][0])
返却値
Short name
Country City
Russia Sankt-Petersburg Spb
USA New-York NY
ここでのゲームに少し遅れましたが、HYRYのソリューションでパフォーマンスの問題が発生していたため、別のソリューションを考え出す必要がありました。
各Key-Valueの頻度を検出し、各キーについて、最も頻繁に表示される値のみを保持することで機能します。
複数のモードをサポートする追加のソリューションもあります。
作業中のデータを代表するスケールテストでは、これによりランタイムが37.4秒から0.5秒に短縮されました。
ソリューションのコード、使用例、およびスケールテストは次のとおりです。
import numpy as np
import pandas as pd
import random
import time
test_input = pd.DataFrame(columns=[ 'key', 'value'],
data= [[ 1, 'A' ],
[ 1, 'B' ],
[ 1, 'B' ],
[ 1, np.nan ],
[ 2, np.nan ],
[ 3, 'C' ],
[ 3, 'C' ],
[ 3, 'D' ],
[ 3, 'D' ]])
def mode(df, key_cols, value_col, count_col):
'''
Pandas does not provide a `mode` aggregation function
for its `GroupBy` objects. This function is meant to fill
that gap, though the semantics are not exactly the same.
The input is a DataFrame with the columns `key_cols`
that you would like to group on, and the column
`value_col` for which you would like to obtain the mode.
The output is a DataFrame with a record per group that has at least one mode
(null values are not counted). The `key_cols` are included as columns, `value_col`
contains a mode (ties are broken arbitrarily and deterministically) for each
group, and `count_col` indicates how many times each mode appeared in its group.
'''
return df.groupby(key_cols + [value_col]).size() \
.to_frame(count_col).reset_index() \
.sort_values(count_col, ascending=False) \
.drop_duplicates(subset=key_cols)
def modes(df, key_cols, value_col, count_col):
'''
Pandas does not provide a `mode` aggregation function
for its `GroupBy` objects. This function is meant to fill
that gap, though the semantics are not exactly the same.
The input is a DataFrame with the columns `key_cols`
that you would like to group on, and the column
`value_col` for which you would like to obtain the modes.
The output is a DataFrame with a record per group that has at least
one mode (null values are not counted). The `key_cols` are included as
columns, `value_col` contains lists indicating the modes for each group,
and `count_col` indicates how many times each mode appeared in its group.
'''
return df.groupby(key_cols + [value_col]).size() \
.to_frame(count_col).reset_index() \
.groupby(key_cols + [count_col])[value_col].unique() \
.to_frame().reset_index() \
.sort_values(count_col, ascending=False) \
.drop_duplicates(subset=key_cols)
print test_input
print mode(test_input, ['key'], 'value', 'count')
print modes(test_input, ['key'], 'value', 'count')
scale_test_data = [[random.randint(1, 100000),
str(random.randint(123456789001, 123456789100))] for i in range(1000000)]
scale_test_input = pd.DataFrame(columns=['key', 'value'],
data=scale_test_data)
start = time.time()
mode(scale_test_input, ['key'], 'value', 'count')
print time.time() - start
start = time.time()
modes(scale_test_input, ['key'], 'value', 'count')
print time.time() - start
start = time.time()
scale_test_input.groupby(['key']).agg(lambda x: x.value_counts().index[0])
print time.time() - start
このコードを実行すると、次のように出力されます。
key value
0 1 A
1 1 B
2 1 B
3 1 NaN
4 2 NaN
5 3 C
6 3 C
7 3 D
8 3 D
key value count
1 1 B 2
2 3 C 2
key count value
1 1 2 [B]
2 3 2 [C, D]
0.489614009857
9.19386196136
37.4375009537
お役に立てれば!
正式には、正しい答えは@eumiroソリューションです。 @HYRYソリューションの問題は、[1,2,3,4]のような数字のシーケンスがある場合、ソリューションが間違っていることです。 e。、modeがありません。例:
import pandas as pd
df = pd.DataFrame({'client' : ['A', 'B', 'A', 'B', 'B', 'C', 'A', 'D', 'D', 'E', 'E', 'E','E','E','A'], 'total' : [1, 4, 3, 2, 4, 1, 2, 3, 5, 1, 2, 2, 2, 3, 4], 'bla':[10, 40, 30, 20, 40, 10, 20, 30, 50, 10, 20, 20, 20, 30, 40]})
@HYRYのように計算すると、次のようになります。
df.groupby(['socio']).agg(lambda x: x.value_counts().index[0])
取得するもの:
これは明らかに間違っています([〜#〜] a [〜#〜]の値は1ではなく4)一意の値で処理できないため。
したがって、他の解決策は正しいです。
import scipy.stats
df3.groupby(['client']).agg(lambda x: scipy.stats.mode(x)[0][0])
取得:
問題 ここ はパフォーマンスです。行が多い場合は問題になります。
あなたの場合は、これを試してください:
import pandas as pd
source = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'],
'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
'Short_name' : ['NY','New','Spb','NY']})
source.groupby(['Country','City']).agg(lambda x:x.value_counts().index[0])
source.groupby(['Country','City']).Short_name.value_counts().groupby['Country','City']).first()
value_counts
やscipy.stats
に依存しない別の解決方法が必要な場合は、Counter
コレクションを使用できます
from collections import Counter
get_most_common = lambda values: max(Counter(values).items(), key = lambda x: x[1])[0]
このように上記の例に適用できます
src = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'],
'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
'Short_name' : ['NY','New','Spb','NY']})
src.groupby(['Country','City']).agg(get_most_common)
大規模なデータセットに対する少し不器用だが高速なアプローチには、対象列のカウントの取得、カウントの最高から最低へのソート、および最大ケースのみを保持するためのサブセットの重複排除が含まれます。
import pandas as pd
source = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'],
'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
'Short name' : ['NY','New','Spb','NY']})
grouped_df = source.groupby(['Country','City','Short name']
)[['Short name']].count().rename(columns={
'Short name':'count'}).reset_index()
grouped_df = grouped_df.sort_values('count',ascending=False)
grouped_df = grouped_df.drop_duplicates(subset=['Country','City']).drop('count', axis=1)
grouped_df
ここでの2つの答えは、次のとおりです。
_df.groupby(cols).agg(lambda x:x.value_counts().index[0])
_
または、できれば
_df.groupby(cols).agg(pd.Series.mode)
_
ただし、以下に示すように、単純なEdgeの場合、これらは両方とも失敗します。
_df = pd.DataFrame({
'client_id':['A', 'A', 'A', 'A', 'B', 'B', 'B', 'C'],
'date':['2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01'],
'location':['NY', 'NY', 'LA', 'LA', 'DC', 'DC', 'LA', np.NaN]
})
_
最初:
_df.groupby(['client_id', 'date']).agg(lambda x:x.value_counts().index[0])
_
IndexError
を生成します(グループC
によって返される空のSeriesのため)。二番目:
_df.groupby(['client_id', 'date']).agg(pd.Series.mode)
_
最初のグループは2つのリストを返すため(2つのモードがあるため)、_ValueError: Function does not reduce
_を返します。 (文書化されているように ここ 、最初のグループが単一モードを返した場合、これは機能します!)
この場合の2つの可能な解決策は次のとおりです。
_import scipy
x.groupby(['client_id', 'date']).agg(lambda x: scipy.stats.mode(x)[0])
_
そして、コメントでcs95によって私に与えられた解決策 ここ :
_def foo(x):
m = pd.Series.mode(x);
return m.values[0] if not m.empty else np.nan
df.groupby(['client_id', 'date']).agg(foo)
_
ただし、これらはすべて低速であり、大規模なデータセットには適していません。私が最終的に使用したソリューションは、a)これらのケースに対処でき、b)はるかに高速で、abw33の答えを少し修正したものです(より高いはずです):
_def get_mode_per_column(dataframe, group_cols, col):
return (dataframe.fillna(-1) # NaN placeholder to keep group
.groupby(group_cols + [col])
.size()
.to_frame('count')
.reset_index()
.sort_values('count', ascending=False)
.drop_duplicates(subset=group_cols)
.drop(columns=['count'])
.sort_values(group_cols)
.replace(-1, np.NaN)) # restore NaNs
group_cols = ['client_id', 'date']
non_grp_cols = list(set(df).difference(group_cols))
output_df = get_mode_per_column(df, group_cols, non_grp_cols[0]).set_index(group_cols)
for col in non_grp_cols[1:]:
output_df[col] = get_mode_per_column(df, group_cols, col)[col]
_
基本的に、このメソッドは一度に1つのcolで機能し、dfを出力します。そのため、集中的なconcat
の代わりに、最初をdfとして扱い、出力配列(values.flatten()
)dfの列として。