GroupBy pandas DataFrame 및 가장 일반적인 값 선택
세 개의 문자열 열이있는 데이터 프레임이 있습니다. 세 번째 열의 유일한 값이 처음 두 가지의 모든 조합에 유효하다는 것을 알고 있습니다. 데이터를 정리하기 위해 데이터 프레임별로 처음 두 열을 그룹화하고 각 조합에 대해 세 번째 열의 가장 일반적인 값을 선택해야합니다.
내 코드 :
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])
2019 답변을 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
항상하는 시리즈를 반환과 매우 호환 만드는 것입니다 agg
및 apply
GROUPBY 출력을 재구성 특히,. 또한 더 빠릅니다.
# 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
여러 모드 가있을 때도 잘 작동 합니다.
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
당신이 경우 상관 없어 그것이 그들 중 하나로서 반환되는 모드로, 당신은 람다 그 호출해야합니다 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
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
... 여러 모드를 처리해야 할 때는 잘 작동하지 않습니다. a StatisticsError
가 발생합니다. 이것은 문서에 언급되어 있습니다.
데이터가 비어 있거나 가장 일반적인 값이 정확히 하나가 없으면 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
두 배열의 튜플을 반환하므로이 튜플에서 첫 번째 배열의 첫 번째 요소를 가져와야합니다.
이 두 가지 간단한 변경 사항 :
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의 솔루션에 성능 문제가 발생하여 다른 솔루션을 찾아야했습니다.
각 키-값의 빈도를 찾은 다음 각 키에 대해 가장 자주 나타나는 값만 유지하는 방식으로 작동합니다.
여러 모드를 지원하는 추가 솔루션도 있습니다.
내가 작업중인 데이터를 대표하는 규모 테스트에서 런타임이 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]와 같은 일련의 숫자가있을 때 솔루션이 잘못되었습니다. 즉, 모드 가 없다는 것 입니다. 예:
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])
그리고 다음을 얻습니다.
고유 한 값으로 처리 할 수 없기 때문에 분명히 잘못된 것입니다 ( 1 이고 4가 아닌 A 값 참조 ).
따라서 다른 솔루션이 정확합니다.
import scipy.stats
df3.groupby(['client']).agg(lambda x: scipy.stats.mode(x)[0][0])
점점 :
A slightly clumsier but faster approach for larger datasets involves getting the counts for a column of interest, sorting the counts highest to lowest, and then de-duplicating on a subset to only retain the largest cases.
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
The problem here is the performance, if you have a lot of rows it will be a problem.
If it is your case, please try with this:
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()
If you want another approach for solving it that is does not depend on value_counts
or scipy.stats
you can use the Counter
collection
from collections import Counter
get_most_common = lambda values: max(Counter(values).items(), key = lambda x: x[1])[0]
Which can be applied to the above example like this
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)
The two top answers here suggest:
df.groupby(cols).agg(lambda x:x.value_counts().index[0])
or, preferably
df.groupby(cols).agg(pd.Series.mode)
However both of these fail in simple edge cases, as demonstrated here:
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]
})
The first:
df.groupby(['client_id', 'date']).agg(lambda x:x.value_counts().index[0])
yields IndexError
(because of the empty Series returned by group C
). The second:
df.groupby(['client_id', 'date']).agg(pd.Series.mode)
returns ValueError: Function does not reduce
, since the first group returns a list of two (since there are two modes). (As documented here, if the first group returned a single mode this would work!)
Two possible solutions for this case are:
import scipy
x.groupby(['client_id', 'date']).agg(lambda x: scipy.stats.mode(x)[0])
And the solution given to me by cs95 in the comments here:
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)
However, all of these are slow and not suited for large datasets. A solution I ended up using which a) can deal with these cases and b) is much, much faster, is a lightly modified version of abw33's answer (which should be higher):
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]
Essentially, the method works on one col at a time and outputs a df, so instead of concat
, which is intensive, you treat the first as a df, and then iteratively add the output array (values.flatten()
) as a column in the df.
참고 URL : https://stackoverflow.com/questions/15222754/groupby-pandas-dataframe-and-select-most-common-value
'development' 카테고리의 다른 글
MKMapView에 단일 핀을 빠르게 추가 하시겠습니까? (0) | 2020.11.29 |
---|---|
소수점 x 자리로 반올림? (0) | 2020.11.29 |
스택 오버플로와 버퍼 오버플로의 차이점은 무엇입니까? (0) | 2020.11.29 |
암호 복구 모범 사례 구현 (0) | 2020.11.29 |
힘내 "당신은 당신의 병합을 완료하지 않았습니다"그리고 커밋 할 것이 없습니까? (0) | 2020.11.29 |