J'ai un ensemble de données avec le nom (nom_personne), le jour et la couleur (chemise_couleur) sous forme de colonnes.
Chaque personne porte une chemise d'une certaine couleur un jour donné. Le nombre de jours peut être arbitraire.
Par exemple. contribution:
name day color
----------------
John 1 White
John 2 White
John 3 Blue
John 4 Blue
John 5 White
Tom 2 White
Tom 3 Blue
Tom 4 Blue
Tom 5 Black
Jerry 1 Black
Jerry 2 Black
Jerry 4 Black
Jerry 5 White
Je dois trouver la couleur la plus fréquemment utilisée par chaque personne.
Par exemple. résultat:
name color
-------------
Jerry Black
John White
Tom Blue
J'effectue l'opération suivante pour obtenir les résultats, ce qui fonctionne bien mais est assez lent:
most_frquent_list = [[name, group.color.mode()[0]]
for name, group in data.groupby('name')]
most_frquent_df = pd.DataFrame(most_frquent_list, columns=['name', 'color'])
Supposons maintenant que j'ai un jeu de données avec 5 millions de noms uniques. Quel est le meilleur/le plus rapide moyen d'effectuer l'opération ci-dessus?
numpy.add.at
et pandas.factorize
de NumpyCeci est destiné à être rapide. Cependant, j'ai essayé de l'organiser pour qu'il soit lisible aussi.
i, r = pd.factorize(df.name)
j, c = pd.factorize(df.color)
n, m = len(r), len(c)
b = np.zeros((n, m), dtype=np.int64)
np.add.at(b, (i, j), 1)
pd.Series(c[b.argmax(1)], r)
John White
Tom Blue
Jerry Black
dtype: object
groupby
, size
et idxmax
df.groupby(['name', 'color']).size().unstack().idxmax(1)
name
Jerry Black
John White
Tom Blue
dtype: object
name
Jerry Black
John White
Tom Blue
Name: color, dtype: object
Counter
¯\_(ツ)_/¯
from collections import Counter
df.groupby('name').color.apply(lambda c: Counter(c).most_common(1)[0][0])
name
Jerry Black
John White
Tom Blue
Name: color, dtype: object
Solution depd.Series.mode
df.groupby('name').color.apply(pd.Series.mode).reset_index(level=1,drop=True)
Out[281]:
name
Jerry Black
John White
Tom Blue
Name: color, dtype: object
METTRE À JOUR
Cela doit être difficile à battre (environ 10 fois plus rapide sur le daraframe échantillon que toute solution de pandas proposée et 1,5 fois plus rapide que la solution numpy proposée). Le Gist consiste à rester à l’écart des pandas et à utiliser itertools.groupby
, qui fait un bien meilleur travail quand il s’agit de données non numériques.
from itertools import groupby
from collections import Counter
pd.Series({x: Counter(z[-1] for z in y).most_common(1)[0][0] for x,y
in groupby(sorted(df.values.tolist()),
key=lambda x: x[0])})
# Jerry Black
# John White
# Tom Blue
Old Answer
Voici une autre méthode. Il est en réalité plus lent que l'original, mais je le garde ici:
data.groupby('name')['color']\
.apply(pd.Series.value_counts)\
.unstack().idxmax(axis=1)
# name
# Jerry Black
# John White
# Tom Blue
Pourquoi ne pas faire deux groupements avec transform(max)
?
df = df.groupby(["name", "color"], as_index=False, sort=False).count()
idx = df.groupby("name", sort=False).transform(max)["day"] == df["day"]
df = df[idx][["name", "color"]].reset_index(drop=True)
Sortie:
name color
0 John White
1 Tom Blue
2 Jerry Black
Similaire aux pd.factorize
et np.add.at
ans de @ piRSquared.
Nous encodons les piqûres dans les colonnes en utilisant
i, r = pd.factorize(df.name)
j, c = pd.factorize(df.color)
n, m = len(r), len(c)
b = np.zeros((n, m), dtype=np.int64)
Mais alors, au lieu de faire ceci:
np.add.at(b, (i, j), 1)
max_columns_after_add_at = b.argmax(1)
Nous obtenons le max_columns_after_add_at
en utilisant une fonction jited, pour ajouter et trouver le maximum dans la même boucle:
@nb.jit(nopython=True, cache=True)
def add_at(x, rows, cols, val):
max_vals = np.zeros((x.shape[0], ), np.int64)
max_inds = np.zeros((x.shape[0], ), np.int64)
for i in range(len(rows)):
r = rows[i]
c = cols[i]
x[r, c]+=1
if(x[r, c] > max_vals[r]):
max_vals[r] = x[r, c]
max_inds[r] = c
return max_inds
Et puis obtenir le dataframe à la fin,
ans = pd.Series(c[max_columns_after_add_at], r)
La différence est donc comment nous fonctionnons argmax(axis=1) after np.add.at()
.
Analyse temporelle
import numpy as np
import numba as nb
m = 100000
n = 100000
rows = np.random.randint(low = 0, high = m, size=10000)
cols = np.random.randint(low = 0, high = n, size=10000)
Donc ça:
%%time
x = np.zeros((m,n))
np.add.at(x, (rows, cols), 1)
maxs = x.argmax(1)
donne:
Temps CPU: utilisateur 12,4 s, sys: 38 s, total: 50,4 s Temps mural: 50,5 s
Et ça
%%time
x = np.zeros((m,n))
maxs2 = add_at(x, rows, cols, 1)
donne
Temps CPU: utilisateur 108 ms, sys: 39.4 s, total: 39.5 s Temps mural: 38.4 s