GroupBy pandas DataFrame 및 가장 일반적인 값 선택


108

세 개의 문자열 열이있는 데이터 프레임이 있습니다. 세 번째 열의 유일한 값이 처음 두 가지의 모든 조합에 유효하다는 것을 알고 있습니다. 데이터를 정리하려면 처음 두 열을 기준으로 데이터 프레임별로 그룹화하고 각 조합에 대해 세 번째 열의 가장 일반적인 값을 선택해야합니다.

내 코드 :

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가 발생합니다. 어떻게 고칠 수 있습니까?

답변:


152

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])

.agg ()에서 다른 agg 기능을 수행하는 것에 대해 궁금한 경우 이것을 시도하십시오.

# Let's add a new col,  account
source['account'] = [1,2,3,3]

source.groupby(['Country','City']).agg(mod  = ('Short name', \
                                        lambda x: x.value_counts().index[0]),
                                        avg = ('account', 'mean') \
                                      )

stats.mode가 문자열 변수의 경우 잘못된 답변을 표시 할 수 있음을 발견했습니다. 이 방법은 더 안정적으로 보입니다.
Viacheslav Nefedov

2
이게 아니야 .value_counts(ascending=False)?
비공개

1
@Private : ascending=False이미 기본값이므로 명시 적으로 순서를 설정할 필요가 없습니다.
Schmuddi

3
Jacquot가 말했듯 pd.Series.mode이 이제는 더 적절하고 빠릅니다.
Daisuke SHIBATO 2008

2
라는 오류가 발생 IndexError: index 0 is out of bounds for axis 0 with size 0했습니다. 해결 방법은 무엇입니까?
rosefun

112

판다> = 0.16

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항상하는 시리즈를 반환과 매우 호환 만드는 것입니다 aggapplyGROUPBY 출력을 재구성 특히,. 또한 더 빠릅니다.

# 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

@JoshFriedlander df.groupby(cols).agg(pd.Series.mode)가 나를 위해 일하는 것 같습니다. 그래도 작동하지 않으면 두 번째 추측은 df.groupby(cols).agg(lambda x: pd.Series.mode(x).values[0]).
cs95

고마워요 (항상 그렇듯이!) 두 번째 옵션은 저를 위해 일을 개선하지만 IndexError: index 0 is out of bounds for axis 0 with size 0(아마도 시리즈에 NaN 만있는 그룹이 있기 때문에) 얻습니다 . 추가 dropna=False로 해결할 수있는 문제 있지만, 인상 것 같다 '<' not supported between instances of 'float' and 'str'(내 일련하는 문자열입니다). (원하는 경우 새 질문으로 만들게되어
Josh Friedlander

2
@JoshFriedlander 정의 def foo(x): m = pd.Series.mode(x); return m.values[0] if not m.empty else np.nan하고 df.groupby(cols).agg(foo). 그래도 작동하지 않으면의 구현을 foo약간 조작하십시오. 여전히 문제가 발생하면 새 Q를 여는 것이 좋습니다.
cs95

2
나는 당신이 계수를 포함하고 싶다면 , 당신이 타이에 대해 신경 쓰지 않고 하나의 모드를 원한다고 가정하고 모드 np.nan를 통해 그것을 할 수 df.groupy(cols).agg(lambda x: x.mode(dropna=False).iloc[0])있다고 추가해야합니다.
irene 20.04.08

17

의 경우 agglambba 함수는 속성 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

1
@ViacheslavNefedov-예,하지만 순수한 판다를 사용하는 @HYRY의 솔루션을 사용하십시오. 에 대한 필요가 없습니다 scipy.stats.
음미로

15

게임에 조금 늦었지만 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

도움이 되었기를 바랍니다!


그게 내가 가장 빠른 길 .. 고마워!
FtoTheZ

1
이 aproach를 사용하는 방법이 있지만 agg 매개 변수 내부에 직접 있습니까?, 예. agg({'f1':mode,'f2':np.sum})
Pablo

1
@PabloA 안타깝게도 인터페이스가 동일하지 않기 때문에 아닙니다. 이 작업을 별도의 작업으로 수행 한 다음 결과를 결합하는 것이 좋습니다. 물론 성능이 문제가되지 않는 경우 HYRY의 솔루션을 사용하여 코드를 더 간결하게 유지할 수 있습니다.
abw333

@ abw333 HYRY의 솔루션을 사용했지만 성능 문제가 발생했습니다. pandas 개발 팀이 agg메서드 에서 더 많은 기능을 지원하기를 바랍니다 .
Pablo

확실히 큰 DataFrames로 이동하는 방법입니다. 8,300 만 개의 행과 250 만 개의 고유 그룹이있었습니다. 열당 28 초가 걸렸고, agg는 열당 11 분 이상 걸렸습니다.
ALollz

6

여기에 두 가지 주요 답변이 제안됩니다.

df.groupby(cols).agg(lambda x:x.value_counts().index[0])

또는, 바람직하게

df.groupby(cols).agg(pd.Series.mode)

그러나이 두 가지 모두 다음과 같이 간단한 경우에 실패합니다.

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])

yields IndexError(group에서 반환 된 빈 Series 때문에 C). 두번째:

df.groupby(['client_id', 'date']).agg(pd.Series.mode)

반환 ValueError: Function does not reduce, 첫 번째 그룹은이 목록을 반환하기 때문에 (두 가지 모드가 있기 때문에). ( 여기 에 설명 된대로 첫 번째 그룹이 단일 모드를 반환하면 작동합니다!)

이 경우에 가능한 두 가지 해결책은 다음과 같습니다.

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].values

기본적으로이 메서드는 한 번에 하나의 열에서 작동하고 df를 출력하므로 concat집중적 인 대신 첫 번째를 df로 처리 한 다음 출력 배열 ( values.flatten())을 df의 열로 반복적으로 추가합니다 .


3

공식적으로 정답은 @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처럼 계산하면 다음을 얻을 수 있습니다.

>>> print(df.groupby(['client']).agg(lambda x: x.value_counts().index[0]))
        total  bla
client            
A           4   30
B           4   40
C           1   10
D           3   30
E           2   20

고유 한 값으로 처리 할 수 ​​없기 때문에 분명히 잘못된 것입니다 ( 1 이고 4가 아닌 A 값 참조 ).

따라서 다른 솔루션이 정확합니다.

>>> import scipy.stats
>>> print(df.groupby(['client']).agg(lambda x: scipy.stats.mode(x)[0][0]))
        total  bla
client            
A           1   10
B           4   40
C           1   10
D           3   30
E           2   20

1

의존하지 않는 다른 접근 방식을 원 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)

이것은 pd.Series.mode또는 보다 빠르지 만 pd.Series.value_counts().iloc[0]계산하려는 NaN 값이 있으면 실패합니다. 각 NaN 발생은 다른 NaN과 다른 것으로 간주되므로 각 NaN은 count를 갖는 것으로 계산 1됩니다. 참조 stackoverflow.com/questions/61102111/...
아이린

1

NaN 값을 포함하지 않으려면를 사용하는 Counter것이 pd.Series.mode또는 보다 훨씬 빠릅니다 pd.Series.value_counts()[0].

def get_most_common(srs):
    x = list(srs)
    my_counter = Counter(x)
    return my_counter.most_common(1)[0][0]

df.groupby(col).agg(get_most_common)

작동합니다. NaN 값이 있으면 각 NaN이 별도로 계산되므로 실패합니다.


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()

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']
        }
    )
>>> grouped_df = source\
        .groupby(['Country','City','Short name'])[['Short name']]\
        .count()\
        .rename(columns={'Short name':'count'})\
        .reset_index()\
        .sort_values('count', ascending=False)\
        .drop_duplicates(subset=['Country', 'City'])\
        .drop('count', axis=1)
>>> print(grouped_df)
  Country              City Short name
1     USA          New-York         NY
0  Russia  Sankt-Petersburg        Spb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.