Les gens qui disent que les pandas sont déjà en désordre sur les PC à faible spécification (Tohoho) gèrent probablement des données dans un désordre avec NumPy. Cependant, NumPy n'a pas de fonction de type pd.DataFrame.groupby
, donc certaines personnes peuvent être dérangées par des pandas en pleurs. Mais si vous êtes stupide et que vous ne pouvez pas faire ce que les pandas peuvent faire avec NumPy, faites de votre mieux pour retourner la documentation NumPy et regarder les différentes fonctions (mais les boucles for sont NG). Oui, vous pouvez.
Pour ceux qui ne savent pas quoi penser, un sage du passé a dit: "Comment agréger par groupe uniquement avec Numpy et calculer la moyenne (pour la phrase, pas de Pandas) / numpy-grouping-no-pandas /) »(Dieu). Cependant, à l'ère actuelle où la science et la technologie se sont développées, j'ai senti qu'il pourrait y avoir une meilleure façon, alors j'ai pensé à diverses choses tout en étudiant les fonctions de Numpy et j'ai écrit une note écrite en japonais qui ne relie pas le récit de la bataille. Cet article est consacré à.
Cette fois, je veux trouver la valeur moyenne et l'écart type pour chaque groupe, c'est-à-dire pour calculer pd.DataFrame.groupby.mean ()
・ pd.DataFrame.groupby.std ()
uniquement avec NumPy.
Données d'iris familières. Les lignes sont mélangées.
import numpy as np
import pandas as pd
df = (pd.read_csv('https://raw.githubusercontent.com/pandas-dev/pandas/'
'master/pandas/tests/data/iris.csv')
.sample(frac=1, random_state=1))
df.head()
SepalLength | SepalWidth | PetalLength | PetalWidth | Name | |
---|---|---|---|---|---|
14 | 5.8 | 4 | 1.2 | 0.2 | Iris-setosa |
98 | 5.1 | 2.5 | 3 | 1.1 | Iris-versicolor |
75 | 6.6 | 3 | 4.4 | 1.4 | Iris-versicolor |
16 | 5.4 | 3.9 | 1.3 | 0.4 | Iris-setosa |
131 | 7.9 | 3.8 | 6.4 | 2 | Iris-virginica |
Une note avant de convertir en un objet Numpy. Afin de gérer efficacement les données avec NumPy, il est essentiel de convertir des données autres que de type numérique (chaîne de caractères, type d'objet, etc.) en type numérique. Si vous souhaitez manipuler des chaînes, il est préférable d'utiliser une liste standard.
La colonne "Nom" ne représente cette fois que la catégorie, et la chaîne de caractères n'a aucune signification dans les données, ce que l'on appelle un codage d'étiquette est effectué pour la convertir en nombre. Ceci est possible avec np.unique (array, return_inverse = True)
.
unique_names, df['Name'] = np.unique(df['Name'].to_numpy(), return_inverse=True)
# (array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'], dtype=object),
# array([0, 1, 1, 0, 2, 1, 2, 0, 0, 2, 1, 0, 2, 1, 1, 0, 1, 1, 0, 0, 1, 1,
# ...,
# 1, 0, 1, 1, 1, 1, 2, 0, 0, 2, 1, 2, 1, 2, 2, 1, 2, 0], dtype=int64))
#C'est également possible
df['Name'], unique_names = pd.factorize(df['Name'], sort=True)
<détails> A ce moment, on sait que Apparemment, vous devriez utiliser Convertir en tableau NumPy (ici, le tableau d'enregistrement est utilisé pour faciliter l'explication). Le moment de rompre avec les pandas. Tout d'abord, je présenterai la méthode de l'article ci-dessus. One-hot vectoriser le type d'iris. Multipliez cela par le vecteur de longueur de pétale que vous souhaitez agréger et prenez la moyenne pour chaque colonne.
Si le vecteur one-hot est moyenné tel quel, il sera affecté par 0, alors remplacez-le par NaN à l'avance. Le problème avec cette méthode est que «np.nanmean ()» et «np.nanstd ()» sont connus pour être lents. La formule moyenne est Par conséquent, si vous le remplacez par une expression qui utilise De plus, Le fait que vous n'ayez pas besoin d'assigner Donc, Le but de groupby est de savoir comment calculer pour chaque groupe.
Dans la partie 1, nous avons pu obtenir le même résultat que Cependant, dans le tableau d'origine, si les éléments sont disposés en groupes, l'idée suivante peut être envisagée. Par conséquent, pour organiser d'abord par groupe, le tri est effectué en fonction de la colonne Nom. Ensuite, la frontière du groupe est obtenue. Cela peut être fait en regardant chaque valeur adjacente dans la colonne Nom triée. Par exemple Si vous obtenez la position Vrai de ce tableau avec Encore une fois, la moyenne est "total ÷ nombre", alors trouvez chacun. Le nombre d'éléments dans un groupe est la différence entre les valeurs adjacentes de Ensuite, la somme des éléments du groupe est calculée. Au fait, il existe une ** fonction universelle Numpy ** qui peut ajouter deux nombres et tableaux appelés Fondamentalement, De plus, Ici, «index» est exactement la «frontière de groupe» recherchée précédemment. En d'autres termes, en résumé Le plus triste à propos de cette technique est que vous ne pouvez pas utiliser correctement Le moyen le plus simple de rendre la partie 1 ou 2 ridicule. Puisque la colonne Nom est un tableau obtenu par encodage d'étiquettes, «{0, 1, 2}» est aligné. Vous pouvez tout compter en même temps en utilisant En utilisant cette pondération, la somme des éléments peut être calculée. Donc, La partie 3 gagne (selon le nombre de groupes). Si vous faites une erreur, veuillez commenter. Souvenons-nous! (Cliquez pour accéder au document officiel)
Recommended Posts
pd.factorize ()
est plus rapide que np.unique ()
quand il y a un certain nombre de données.import perfplot
test_df = pd.DataFrame(['a'])
perfplot.show(
setup=lambda n: pd.concat([test_df]*n,
ignore_index=True, axis=0).iloc[:, 0],
n_range=[2 ** k for k in range(20)],
kernels=[
lambda series: np.unique(series.to_numpy(), return_inverse=True),
lambda series: pd.factorize(series, sort=True),
],
labels=["np.unique", "pd.factorize"],
equality_check=None,
xlabel="Nombre de lignes de données", logx=True, logy=True, time_unit="s", automatic_order=False
)
pd.factorize ()
lors de la conversion de Series
(pas seulement ndarray
) avec plus de 1000 lignes. </ div> </ détails>arr = df.to_records(index=False)
Comment grouper par
Partie 1 (onehot)
Partie 1
onehot = np.eye(arr['Name'].max()+1)[arr['Name']]
onehot[onehot == 0] = np.nan
onehot_value = onehot * arr['PetalWidth'][:, None]
result_mean = np.nanmean(onehot_value, 0)
# [0.244 1.326 2.026]
result_std = np.nanstd(onehot_value, 0)
# [0.10613199 0.19576517 0.27188968]
mean = \frac{1}{n} \sum_{i=1}^{N} x_i
np.nansum ()
, ce sera comme suit.python
result_mean = np.nansum(onehot_value, 0)/(~np.isnan(onehot_value)).sum(0)
result_std = np.sqrt(np.nansum(onehot_value**2, 0)
/ (~np.isnan(onehot_value)).sum(0) - result_mean**2)
np.nan
dans ʻonehot_value remplace
0, mais comme le total ne change pas quel que soit le nombre de
0s ajoutés, ne le remplacez pas par
np.nan. Vous pouvez utiliser
np.sum ()` tel quel.np.nan
à ʻonehot signifie que vous n'avez pas à utiliser le type de données
float, donc vous pouvez obtenir ʻonehot
plus rapidement par la méthode suivante.%timeit np.eye(arr['Name'].max()+1)[arr['Name']]
# 21.2 µs ± 919 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# array([[1., 0., 0.],
# [0., 1., 0.],
# [0., 1., 0.],
# ...,
# [1., 0., 0.]])
%timeit np.arange(arr['Name'].max()+1) == arr['Name'][:, None]
# 20.1 µs ± 205 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# array([[ True, False, False],
# [False, True, False],
# [False, True, False],
# ...,
# [ True, False, False]])
Partie 1 pause
onehot = np.arange(arr['Name'].max()+1) == arr['Name'][:, None]
onehot_value = onehot * arr['PetalWidth'][:, None]
sums = onehot_value.sum(0)
counts = onehot.sum(0)
result_mean = sums/counts
result_std = np.sqrt((onehot_value**2).sum(0)/counts - result_mean**2)
Partie 2 (réduire)
groupby.mean ()
en déplaçant les valeurs dans différentes colonnes pour chaque groupe et en les faisant la moyenne.Partie 1 Façon de penser
g | A B B C A B A C C
v
A | 1 0 0 0 1 0 1 0 0 | mean_A
B | 0 1 1 0 0 1 0 0 0 | -> mean_B
C | 0 0 0 1 0 0 0 1 1 | mean_C
Partie 2 façon de penser
g | A A A B B B C C C
----A--- ----B--- ----C---
v
mean_A mean_B mean_C
sorter_index = np.argsort(arr['Name'])
name_s = arr['Name'][sorter_index]
# array([0, 0, 0, ..., 1, 1, 1, ..., 2, 2, 2], dtype=int64)
pwidth_s = arr['PetalWidth'][sorter_index]
ex_array = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3])
# 0------ 1------ 2------ 3------← Je veux la position de cette frontière
ex_array[1:] != ex_array[1:]
"""Façon de penser
0 [0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3] # ex_array[1:]
[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3] 3 # ex_array[:-1]
[F, F, T, F, F, T, F, F, T, F, F] # ex_array[1:] != ex_array[1:]
(Si les deux premiers correspondent, F,S'ils ne correspondent pas, T)
[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3] # ex_array
[T, F, F, T, F, F, T, F, F, T, F, F, T] #Le tableau que je voulais vraiment
0------ 1------ 2------ 3------
# ex_array[1:] != ex_array[1:]Si vous ajoutez un Vrai avant et après,
"T-avant le prochain T" devient un groupe.
"""
#Version complémentaire avant et après
np.concatenate(([True], ex_array[1:] != ex_array[:-1], [True]))
# array([ True, False, False, False, False, False, False, False, False, False, False, False, True])
np.ndarray.nonzero ()
, ce sera la position de la limite de chaque groupe.cut_index = np.concatenate(([True], ex_array[1:] != ex_array[:-1], [True])).nonzero()[0]
# array([ 0, 3, 6, 9, 12], dtype=int64)
"""Façon de penser
0 1 2 3 4 5 6 7 8 9 10 11 12 # ex_index du tableau
[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3] # ex_array
[0, 3, 6, 9, 12] # cut_index:Limite (point de départ) de chaque groupe
"""
cut_index
, vous pouvez donc utiliser np.diff () ʻet
np.ediff1d ()` pour le trouver d'un seul coup.counts = np.ediff1d(cut_index)
# array([3, 3, 3, 3], dtype=int64)
np.add (x1, x2)
.np.add(1, 2)
# 3
np.add([0, 1], [0, 2])
# array([0, 3])
np.add ()
est juste +
, il est donc peu probable que vous utilisiez cette fonction elle-même. Ce qui est important, c'est la méthode de la fonction universelle. Par exemple, il y a np.ufunc.reduce (array)
, qui vous permet d'extraire des éléments de ʻarray` et d'appliquer des fonctions. Qu'est-ce que cela signifie, par exempleex_array = np.arange(10)
result = ex_array[0]
for value in ex_array[1:]:
result = np.add(result, value)
print(result)
# 45
np.add.reduce(ex_array)
# 45
np.ufunc.reduceat (tableau, indices)
donne un tableau de np.ufunc.reduce (a [indices [i]: indices [i + 1]])
.ex_array = np.arange(10)
indices = [0, 1, 6, len(ex_array)]
results = []
for i in range(len(indices) - 1):
result = np.add.reduce(ex_array[indices[i]:indices[i+1]])
results.append(result)
print(results)
# [0, 15, 30]
indices = [0, 1, 6]
np.add.reduceat(ex_array, indices)
# array([0, 15, 30], dtype=int32)
Partie 2
sorter_index = np.argsort(arr['Name'])
name_s = arr['Name'][sorter_index]
pwidth_s = arr['PetalWidth'][sorter_index]
cut_index, = np.concatenate(([True], name_s[1:] != name_s[:-1], [True])).nonzero()
sums = np.add.reduceat(pwidth_s, cut_index[:-1])
counts = np.ediff1d(cut_index)
result_mean = sums/counts
# [0.244 1.326 2.026]
result_std = np.array([np.std(pwidth_s[s:e], ddof=0)
for s, e in zip(cut_index[:-1], cut_index[1:])])
# [0.10613199 0.19576517 0.27188968]
np.ufunc.reduceat ()
pour trouver l'écart type, vous devez donc vous fier à la boucle for (à la place, utilisez np.std ()
directement. Peut être utilisé).Partie 3 (bincount)
np.bincount ()
. Qu'est-ce que cela signifie, par exemple?ex_array = np.array([3, 4, 3, 3, 2, 2, 4, 4, 1, 3])
results = []
for value in range(ex_array.max()+1):
result = sum(ex_array == value)
results.append(result)
print(results)
# [0, 1, 2, 4, 3]
# ex_Dans le tableau, 0 vaut 0, 1 vaut 1, 2 vaut 2, 3 vaut 4 et 4 vaut 3.
np.bincount(ex_array)
# array([0, 1, 2, 4, 3], dtype=int64)
np.bincount ()
a une option poids
qui permet la pondération. Ainsi, par exemple,ex_array = np.array([1, 2, 2, 3, 3, 3, 3, 4, 4, 4])
weights = np.array([2, 1, 1, 1, 1, 1, 1, 2, 3, 4])
results = []
for value in range(ex_array.max()+1):
idx = np.nonzero(ex_array == value)[0]
result = sum(weights[idx])
results.append(result)
print(results)
# [0, 2, 2, 4, 9]
np.bincount(ex_array, weights)
# array([0., 2., 2., 4., 9.])
Partie 3
counts = np.bincount(arr['Name'])
sums = np.bincount(arr['Name'], arr['PetalWidth'])
result_mean = sums/counts
# [0.244 1.326 2.026]
deviation_array = result_mean[arr['Name']] - arr['PetalWidth']
result_std = np.sqrt(np.bincount(arr['Name'], deviation_array**2) / counts)
# [0.10613199 0.19576517 0.27188968]
Résumé (les personnes qui ont été capturées dans le titre doivent lire uniquement ici)
numpy_groupby.py
import numpy as np
import pandas as pd
import perfplot
def type1(value, group):
onehot = np.eye(group.max()+1)[group]
onehot[onehot == 0] = np.nan
onehot_value = onehot * value[:, None]
result_mean = np.nanmean(onehot_value, 0)
result_std = np.nanstd(onehot_value, 0)
return result_mean, result_std
def type1rev(value, group):
onehot = np.arange(group.max()+1) == group[:, None]
onehot_value = onehot * value[:, None]
sums = onehot_value.sum(0)
counts = onehot.sum(0)
result_mean = sums/counts
result_std = np.sqrt((onehot_value**2).sum(0)/counts - result_mean**2)
return result_mean, result_std
def type2(value, group):
sorter_index = np.argsort(group)
group_s = group[sorter_index]
value_s = value[sorter_index]
cut_index, = np.concatenate(([True], group_s[1:] != group_s[:-1], [True])).nonzero()
sums = np.add.reduceat(value_s, cut_index[:-1])
counts = np.ediff1d(cut_index)
result_mean = sums/counts
result_std = np.array([np.std(value_s[s:e], ddof=0)
for s, e in zip(cut_index[:-1], cut_index[1:])])
return result_mean, result_std
def type3(value, group):
counts = np.bincount(group)
sums = np.bincount(group, value)
result_mean = sums/counts
result_std = np.sqrt(
np.bincount(group, (result_mean[group] - value)**2) / counts)
return result_mean, result_std
df = (pd.read_csv('https://raw.githubusercontent.com/pandas-dev/pandas/'
'master/pandas/tests/data/iris.csv')
.sample(frac=1, random_state=1))
unique_names, df['Name'] = np.unique(df['Name'].to_numpy(), return_inverse=True)
arr = df.to_records(index=False)[:5]
#Comparaison
perfplot.show(
setup=lambda n: np.concatenate([arr]*n),
n_range=[2 ** k for k in range(21)],
kernels=[
lambda arr: type1(arr['PetalWidth'], arr['Name']),
lambda arr: type1rev(arr['PetalWidth'], arr['Name']),
lambda arr: type2(arr['PetalWidth'], arr['Name']),
lambda arr: type3(arr['PetalWidth'], arr['Name']),
],
labels=["type1", "type1rev", "type2", "type3"],
equality_check=None,
xlabel="Nombre de lignes de données (× 5)", logx=True, logy=True,
time_unit="s", automatic_order=False
)
Bonus: fonctions apparues cette fois
np.nansum ()
--Traite le non-nombre (NaN) comme zéro, Renvoie la somme des éléments du tableau sur l'axe spécifié.
np.nanmean ()
- Ignorez NaN et déplacez-vous vers l'axe spécifié Calculez la moyenne arithmétique en suivant.
np.nanstd ()
- Ignorez NaN et déplacez-vous vers l'axe spécifié Calculez l'écart type le long.np.add ()
--Ajoutez des arguments élément par élément.
np.ufunc.reduce ()
- Le long d'un axe En appliquant ufunc, la dimension du tableau est réduite de un.
np.ufunc.reduceat ()
sur un seul axe Effectue une réduction (locale) à l'aide de la tranche spécifiée.np.argsort ()
- Renvoie l'index pour trier le tableau.
np.nonzero ()
- Renvoie l'index des éléments non nuls.np.bincount ()
--Chaque valeur dans un tableau composé uniquement de nombres naturels Compte le nombre d'occurrences de.
np.unique ()
-Trouver des éléments uniques du tableau.np.ediff1d ()
- Différences entre les éléments consécutifs d'un tableau.np.concatenate ()
- Combinez des séquences de séquences le long des axes existants Faire.