web-dev-qa-db-fra.com

Pandas dataframe obtient la valeur de la dernière colonne différente de zéro

J'ai un cadre de données pandas qui contient 3 colonnes, chacune contenant un site visité par un utilisateur au cours d'une session.

Dans certains cas, un utilisateur peut ne pas avoir visité 3 sites au cours d'une même session. Ceci est indiqué par un 0, indiquant qu'aucun site n'a été visité. 

import pandas as pd

df = pd.DataFrame(data=[[5, 8, 1],[8,0,0],[1,17,0]], 
                  columns=['site1', 'site2', 'site3'])
print(df)

   site1  site2  site3
0      5      8      1
1      8      0      0
2      1     17      0

Dans l'exemple ci-dessus, l'utilisateur 0 a visité les sites 5, 8 et 1. L'utilisateur 1 n'a visité que le site 8 et l'utilisateur 2 a visité les sites 1 et 17.

J'aimerais créer une nouvelle colonne, last_site, qui montre le dernier site visité par l'utilisateur au cours de cette session.

Le résultat que je veux est le suivant:

   site1  site2  site3  last_site
0      5      8      1          1
1      8      0      0          8
2      1     17      0         17

Comment puis-je le faire de manière concise en utilisant des pandas?

9
kskyriacou

Utilisez le remplissage en avant des valeurs d'erreur créées en remplaçant les valeurs 0 et en sélectionnant la dernière colonne par iloc:

df['last'] = df.replace(0, np.nan).ffill(axis=1).iloc[:, -1].astype(int)
print (df)
   site1  site2  site3  last
0      5      8      1     1
1      8      0      0     8
2      1     17      0    17

Si les performances sont importantes, utilisez numpy:

a = df.values
m = a != 0

df['last'] = a[np.arange(m.shape[0]), m.shape[1]-m[:,::-1].argmax(1)-1]
print (df)
   site1  site2  site3  last
0      5      8      1     1
1      8      0      0     8
2      1     17      0    17
14
jezrael

Code:

df['last_site'] = df.apply(lambda x: x.iloc[x.nonzero()].iloc[-1], axis=1)

Sortie:

   site1  site2  site3  last_site
0      5      8      1          1
1      8      0      0          8
2      1     17      0         17
8
Vishnudev

mask + ffill

Une solution "pur Pandas":

df['last'] = df.mask(df.eq(0)).ffill(1).iloc[:, -1].astype(int)

numba

Pour une efficacité sur un grand nombre de lignes/colonnes, numba peut vous aider. Pour savoir pourquoi cela fonctionne mieux que argmax, voir Renvoie efficacement l'index de la première condition satisfaisant dans array .

from numba import njit

@njit
def get_last_val(A):
    m, n = A.shape
    res = A[:, -1]
    for i in range(m):
        for j in range(n):
            if A[i, j] == 0:
                res[i] = A[i, max(0, j-1)]
                break
    return res

df['last'] = get_last_val(df.values)
2
jpp