Comment regrouper une colonne biaisée à différents intervalles d'une autre chez les pandas?
J'ai le pd.DataFrame suivant:
source = pd.DataFrame([[0.99, 0.98, 0.93, 0.81, 0.85, 0.71, 0.7, 0.69, 0.68, 0.66],
[100, 12, 312, 23, 2, 12, 32, 21, 21, 21]]).T
Je souhaite le convertir le plus rapidement possible en:
desired_result = pd.DataFrame([[0.9, 0.8, 0.7, 0.6], [424, 25, 44, 63]]).T
Où ci-dessus, je définis un intervalle 0.1
dont j'applique à la colonne 0
de la source
dataframe et additionne la 1
colonne de la même dataframe. L'idée est que cela devrait fonctionner avec des intervalles différents.
Ce que j'ai essayé:
J'ai pensé à l'utiliser
pd.cut
mais cela ne semble pas être ce que je recherche.Je sais que si j'ajoute une nouvelle colonne à
source
laquelle contient des valeurs dupliquées de 0,9, 0,8, 0,7 et 0,6 sur les lignes correspondantes, je peux l'utilisergroupby
sur cette nouvelle colonne et ensuitesum
, mais je me demande s'il existe un moyen plus propre et plus rapide pour faire ça? par exemple, comme ceci:
interval = 0.1
source['ints'] = (source[0] / interval).astype(int)
result = source.groupby(source['ints']).sum().reset_index()
result
Cependant, ce qui précède ne fonctionnerait pas si je devais changer la forme d'intervalle de 0,1 à 0,05 par exemple.
Toute aide serait appréciée.
Réponses
Pour la vitesse: essayez toujours de vectoriser tout ce que vous pouvez et évitez apply
autant que possible.
Voici un moyen plus rapide (crédit à @DavidErickson pour sort=False
):
interval = 0.1
source.groupby(np.trunc(source[0] / interval) * interval, sort=False)[1].sum().reset_index()
# out:
0 1
0 0.9 424.0
1 0.8 25.0
2 0.7 12.0
3 0.6 95.0
La différence de vitesse peut être assez dramatique pour les gros df
.
Essayez avec 1 million de lignes, regroupées dans 10 000 cases:
source = pd.DataFrame(np.random.normal(scale=1000, size=(int(1e6), 2)))
%%timeit
# ... (as above)
26.7 ms ± 292 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Avec un à la apply
place:
1.51 s ± 11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
(50x plus lent).
Vous pouvez utiliser une custom_round
fonction à partir de laquelle j'ai effectué 3 modificationshttps://stackoverflow.com/a/40372261/6366770:
- J'ai utilisé
np.floor
au lieu deround
comme tu veux descendre. - Cela gâche les valeurs qui sont sur la «bordure» d'un bac, donc j'ajoute
+ base/100
(ce0.9
serait le cas0.9 + .009 = 0.909
et arrondir à 0,9 au lieu de incorrectement à 0,8), de sorte qu'il soit juste au-dessus de la bordure et arrondisse correctement. Je pense que cela vous couvrira. Vous pouvez le faire1 / 1000
pour être en sécurité. - La réponse que je partage était à la recherche
int
, tellement suppriméeint
, car nous cherchons à arrondir les flotteurs
source = pd.DataFrame(np.random.normal(scale=1000, size=(int(1e6), 2)))
def custom_round(x, y, base):
return source.groupby((base * np.floor((x + (base / 100)) / base)), sort=False)[y].sum()
%timeit custom_round(source[0], 1, .1)
89.8 ms ± 1.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Sur mon ordinateur, la réponse acceptée est plus lente:
102 ms ± 1.86 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)