wake-up-neo.com

GroupBy Pandas DataFrame und wählen Sie den häufigsten Wert aus

Ich habe einen Datenrahmen mit drei String-Spalten. Ich weiß, dass der einzige Wert in der 3. Spalte für jede Kombination der ersten beiden Werte gilt. Um die Daten zu bereinigen, muss ich nach Datenrahmen nach ersten zwei Spalten gruppieren und den am häufigsten verwendeten Wert der dritten Spalte für jede Kombination auswählen.

Mein Code:

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

Die letzte Codezeile funktioniert nicht, sie sagt "Schlüsselfehler 'Kurzname'" und wenn ich versuche, nur nach Stadt zu gruppieren, habe ich einen AssertionError. Was kann ich beheben?

41

Sie können value_counts() verwenden, um eine Zählreihe zu erhalten und die erste Zeile abzurufen:

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

Für agg erhält die Lambba-Funktion eine Series, die kein 'Short name'-Attribut besitzt.

stats.mode gibt einen Tupel aus zwei Arrays zurück, daher müssen Sie das erste Element des ersten Arrays in diesem Tuple nehmen.

Mit diesen zwei einfachen Änderungen:

source.groupby(['Country','City']).agg(lambda x: stats.mode(x)[0][0])

kehrt zurück

                         Short name
Country City                       
Russia  Sankt-Petersburg        Spb
USA     New-York                 NY
15
eumiro

Ein wenig zu spät zum Spiel hier, aber ich hatte einige Probleme mit der HYRY-Lösung, daher musste ich mir ein anderes vorstellen.

Sie ermittelt die Häufigkeit jedes Schlüsselwerts und behält dann für jeden Schlüssel nur den Wert bei, der am häufigsten angezeigt wird.

Es gibt auch eine zusätzliche Lösung, die mehrere Modi unterstützt.

Bei einem Skalentest, der für die Daten, mit denen ich arbeite, repräsentativ ist, wurde die Laufzeit von 37,4 Sekunden auf 0,5 Sekunden reduziert.

Hier ist der Code für die Lösung, einige Anwendungsbeispiele und der Skalentest:

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

Wenn Sie diesen Code ausführen, wird Folgendes gedruckt:

   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

Hoffe das hilft!

9
abw333

Formell ist die richtige Antwort die @eumiro-Lösung. Das Problem der @HYRY-Lösung besteht darin, dass die Lösung falsch ist, wenn Sie eine Zahlenfolge wie [1,2,3,4] haben. Sie haben nicht den Modus. Beispiel:

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

Wenn Sie wie @HYRY berechnen, erhalten Sie:

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

und du erhältst:

 enter image description here

Das ist eindeutig falsch (siehe den A -Wert, der 1 und nicht 4 sein sollte, da er mit eindeutigen Werten nicht umgehen kann.

Somit ist die andere Lösung richtig:

import scipy.stats
df3.groupby(['client']).agg(lambda x: scipy.stats.mode(x)[0][0])

bekommen:

 enter image description here

3
NunodeSousa

2019 Antwort, pd.Series.mode ist verfügbar.

Verwenden Sie groupby , GroupBy.agg , und wenden Sie die Funktion pd.Series.mode auf jede Gruppe an:

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

Wenn dies als DataFrame erforderlich ist, verwenden Sie

source.groupby(['Country','City'])['Short name'].agg(pd.Series.mode).to_frame()

                         Short name
Country City                       
Russia  Sankt-Petersburg        Spb
USA     New-York                 NY

Das Nützliche an Series.mode ist, dass immer eine Serie zurückgegeben wird, sodass sie sehr gut mit agg und apply kompatibel ist, insbesondere beim Rekonstruieren der Group-Ausgabe. Es ist auch schneller.

# 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 macht auch gute Arbeit, wenn es multiple Modi gibt:

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

Wenn Sie für jeden Modus eine eigene Zeile wünschen, können Sie GroupBy.apply verwenden:

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

Wenn Ihnen egal egal ist, welcher Modus zurückgegeben wird, solange er einer von ihnen ist, benötigen Sie ein Lambda, das mode aufruft und das erste Ergebnis extrahiert.

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

Sie können auch statistics.mode aus Python verwenden, aber ...

source.groupby(['Country','City'])['Short name'].apply(statistics.mode)

Country  City            
Russia   Sankt-Petersburg    Spb
USA      New-York             NY
Name: Short name, dtype: object

... es funktioniert nicht gut, wenn Sie mit mehreren Modi umgehen müssen; eine StatisticsError wird ausgelöst. Dies wird in den Dokumenten erwähnt:

Wenn Daten leer sind oder wenn nicht genau ein häufigster Wert vorhanden ist, StatisticsError wird ausgelöst.

Aber du kannst es selbst sehen ...

statistics.mode([1, 2])
# ---------------------------------------------------------------------------
# StatisticsError                           Traceback (most recent call last)
# ...
# StatisticsError: no unique mode; found 2 equally common values
3
coldspeed

Das Problem hier ist die Leistung. Wenn Sie viele Zeilen haben, wird dies ein Problem sein. 

Wenn es dein Fall ist, versuche es bitte mit:

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

Wenn Sie einen anderen Lösungsweg suchen, der nicht von value_counts oder scipy.stats abhängig ist, können Sie die Counter-Sammlung verwenden

from collections import Counter
get_most_common = lambda values: max(Counter(values).items(), key = lambda x: x[1])[0]

Was auf das obige Beispiel so angewendet werden kann

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)
0
kmader

Ein etwas unbeholfeneres, aber schnelleres Verfahren für größere Datensätze besteht darin, die Zählungen für eine interessierende Spalte zu ermitteln, die Zählungen von Höchst zu niedrigsten zu sortieren und anschließend in einer Teilmenge zu duplizieren, um nur die größten Fälle beizubehalten.

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
0
Dimitri

Die zwei Top-Antworten hier schlagen vor:

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

oder vorzugsweise

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

Beide schlagen jedoch in einfachen Edge-Fällen fehl, wie hier gezeigt:

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

Der Erste:

df.groupby(['client_id', 'date']).agg(lambda x:x.value_counts().index[0])

ergibt IndexError (wegen der von der Gruppe C zurückgegebenen leeren Serie). Der Zweite:

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

gibt ValueError: Function does not reduce zurück, da die erste Gruppe eine Liste von zwei zurückgibt (da es zwei Modi gibt). (Wie dokumentiert hier , wenn die erste Gruppe einen einzelnen Modus zurückgibt, würde dies funktionieren!)

Zwei mögliche Lösungen für diesen Fall sind:

import scipy
x.groupby(['client_id', 'date']).agg(lambda x: scipy.stats.mode(x)[0])

Und die Lösung, die mir cs95 in den Kommentaren gegeben hat hier :

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)

All dies ist jedoch langsam und nicht für große Datenmengen geeignet. Eine Lösung, mit der ich am Ende a) diese Fälle behandeln kann und b) viel, viel schneller ist, ist eine leicht modifizierte Version der Antwort von abw33 (die höher sein sollte):

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]

Im Wesentlichen arbeitet die Methode mit jeweils einer Spalte und gibt ein df aus. Anstelle von concat, das intensiv ist, wird das erste als df behandelt und anschließend das Ausgabearray iterativ hinzugefügt (values.flatten() ) als spalte im df.

0