Comment regrouper une colonne biaisée à différents intervalles d'une autre chez les pandas?

Dec 12 2020

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.1dont j'applique à la colonne 0de la sourcedataframe et additionne la 1colonne de la même dataframe. L'idée est que cela devrait fonctionner avec des intervalles différents.

Ce que j'ai essayé:

  1. J'ai pensé à l'utiliser pd.cutmais cela ne semble pas être ce que je recherche.

  2. Je sais que si j'ajoute une nouvelle colonne à sourcelaquelle contient des valeurs dupliquées de 0,9, 0,8, 0,7 et 0,6 sur les lignes correspondantes, je peux l'utiliser groupbysur cette nouvelle colonne et ensuite sum, 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

3 PierreD Dec 12 2020 at 21:28

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 applyplace:

1.51 s ± 11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

(50x plus lent).

1 DavidErickson Dec 12 2020 at 20:55

Vous pouvez utiliser une custom_roundfonction à partir de laquelle j'ai effectué 3 modificationshttps://stackoverflow.com/a/40372261/6366770:

  1. J'ai utilisé np.floorau lieu de roundcomme tu veux descendre.
  2. Cela gâche les valeurs qui sont sur la «bordure» d'un bac, donc j'ajoute + base/100(ce 0.9serait le cas 0.9 + .009 = 0.909et 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 faire 1 / 1000pour être en sécurité.
  3. La réponse que je partage était à la recherche int, tellement supprimée int, 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)