2015-02-26 23 views
5

Ho un dataframe panda che assomiglia a questo:Generazione di una coorte di ritenzione da un dataframe panda

+-----------+------------------+---------------+------------+ 
| AccountID | RegistrationWeek | Weekly_Visits | Visit_Week | 
+-----------+------------------+---------------+------------+ 
| ACC1  | 2015-01-25  |    0 | NaT  | 
| ACC2  | 2015-01-11  |    0 | NaT  | 
| ACC3  | 2015-01-18  |    0 | NaT  | 
| ACC4  | 2014-12-21  |   14 | 2015-02-12 | 
| ACC5  | 2014-12-21  |    5 | 2015-02-15 | 
| ACC6  | 2014-12-21  |    0 | 2015-02-22 | 
+-----------+------------------+---------------+------------+ 

E 'essenzialmente una registrazione delle visite di sorta, in quanto contiene tutti i dati necessari per la creazione di un'analisi di coorte.

Ogni settimana di registrazione è una coorte. Per sapere quante persone fanno parte della coorte posso usare:

visit_log.groupby('RegistrationWeek').AccountID.nunique() 

Quello che voglio fare è creare una tabella pivot con le settimane di registrazione come chiavi. Le colonne dovrebbero essere visit_weeks e i valori dovrebbero essere il conteggio degli ID account univoci che hanno più di 0 visite settimanali.

Insieme ai conti totali di ogni coorte, sarà quindi in grado di mostrare le percentuali invece dei valori assoluti.

Il prodotto finale sarebbe simile a questa:

+-------------------+-------------+-------------+-------------+ 
| Registration Week | Visit_week1 | Visit_Week2 | Visit_week3 | 
+-------------------+-------------+-------------+-------------+ 
| week1    | 70%   | 30%   | 20%   | 
| week2    | 70%   | 30%   |    | 
| week3    | 40%   |    |    | 
+-------------------+-------------+-------------+-------------+ 

ho provato ruotando la dataframe in questo modo:

visit_log.pivot_table(index='RegistrationWeek', columns='Visit_Week') 

ma non ho inchiodato la parte di valore. Dovrò in qualche modo contare l'ID account e dividere la somma per l'aggregazione della settimana di registrazione dall'alto.

Sono nuovo per i panda quindi se questo non è il modo migliore per fare coorti di conservazione, per favore illuminami!

Grazie

+0

È possibile incollare un campione di DataFrame in una tabella HTML valida? Ciò consentirebbe ad altri di leggerlo in panda per rispondere alle domande del QA. –

risposta

9

Ci sono diversi aspetti alla tua domanda.

Che cosa si può costruire con i dati che avete

ci sono several kinds of retention. Per semplicità, ne menzioniamo solo due:

  • Ritenzione giorno-n: se un utente si è registrato il giorno 0, ha effettuato l'accesso il giorno N? (La registrazione nel giorno N + 1 non ha effetto su questa metrica). Per misurarlo, è necessario tenere traccia di tutti i registri dei propri utenti.
  • Ritenzione di rotazione: se un utente si è registrato il giorno 0, ha effettuato l'accesso il giorno N o in qualsiasi giorno successivo a quello? (L'accesso al giorno N + 1 influisce su questa metrica). Per misurarlo, hai solo bisogno degli ultimi registri di conoscenza dei tuoi utenti.

Se comprendo correttamente la tabella, sono disponibili due variabili rilevanti per creare la tabella di coorte: data di registrazione e ultimo registro (visita settimana). Il numero di visite settimanali sembra irrilevante.

Quindi con questo si può andare solo con l'opzione 2, mantenimento del rotolamento.

Come costruire la tabella

In primo luogo, cerchiamo di costruire un insieme di dati fittizi set in modo da avere abbastanza per lavorare e si può riprodurlo:

import pandas as pd 
import numpy as np 
import math 
import datetime as dt 

np.random.seed(0) # so that we all have the same results 

def random_date(start, end,p=None): 
    # Return a date randomly chosen between two dates 
    if p is None: 
     p = np.random.random() 
    return start + dt.timedelta(seconds=math.ceil(p * (end - start).days*24*3600)) 

n_samples = 1000 # How many users do we want ? 
index = range(1,n_samples+1) 

# A range of signup dates, say, one year. 
end = dt.datetime.today() 
from dateutil.relativedelta import relativedelta 
start = end - relativedelta(years=1) 

# Create the dataframe 
users = pd.DataFrame(np.random.rand(n_samples), 
        index=index, columns=['signup_date']) 
users['signup_date'] = users['signup_date'].apply(lambda x : random_date(start, end,x)) 
# last logs randomly distributed within 10 weeks of singing up, so that we can see the retention drop in our table 
users['last_log'] = users['signup_date'].apply(lambda x : random_date(x, x + relativedelta(weeks=10))) 

Così ora dovremmo avere qualcosa che assomiglia a questo:

users.head() 

enter image description here

Ecco il codice per costruire una tabella di coorte:

### Some useful functions 
def add_weeks(sourcedate,weeks): 
    return sourcedate + dt.timedelta(days=7*weeks) 

def first_day_of_week(sourcedate): 
    return sourcedate - dt.timedelta(days = sourcedate.weekday()) 

def last_day_of_week(sourcedate): 
    return sourcedate + dt.timedelta(days=(6 - sourcedate.weekday())) 

def retained_in_interval(users,signup_week,n_weeks,end_date): 
    ''' 
     For a given list of users, returns the number of users 
     that signed up in the week of signup_week (the cohort) 
     and that are retained after n_weeks 
     end_date is just here to control that we do not un-necessarily fill the bottom right of the table 
    ''' 
    # Define the span of the given week 
    cohort_start  = first_day_of_week(signup_week) 
    cohort_end   = last_day_of_week(signup_week) 
    if n_weeks == 0: 
     # If this is our first week, we just take the number of users that signed up on the given period of time 
     return len(users[(users['signup_date'] >= cohort_start) 
         & (users['signup_date'] <= cohort_end)]) 
    elif pd.to_datetime(add_weeks(cohort_end,n_weeks)) > pd.to_datetime(end_date) : 
     # If adding n_weeks brings us later than the end date of the table (the bottom right of the table), 
     # We return some easily recognizable date (not 0 as it would cause confusion) 
     return float("Inf") 
    else: 
     # Otherwise, we count the number of users that signed up on the given period of time, 
     # and whose last known log was later than the number of weeks added (rolling retention) 
     return len(users[(users['signup_date'] >= cohort_start) 
         & (users['signup_date'] <= cohort_end) 
         & pd.to_datetime((users['last_log']) >= pd.to_datetime(users['signup_date'].map(lambda x: add_weeks(x,n_weeks)))) 
         ]) 

Con questo siamo in grado di creare la funzione attuale:

def cohort_table(users,cohort_number=6,period_number=6,cohort_span='W',end_date=None): 
    ''' 
     For a given dataframe of users, return a cohort table with the following parameters : 
     cohort_number : the number of lines of the table 
     period_number : the number of columns of the table 
     cohort_span : the span of every period of time between the cohort (D, W, M) 
     end_date = the date after which we stop counting the users 
    ''' 
    # the last column of the table will end today : 
    if end_date is None: 
     end_date = dt.datetime.today() 
    # The index of the dataframe will be a list of dates ranging 
    dates = pd.date_range(add_weeks(end_date,-cohort_number), periods=cohort_number, freq=cohort_span) 

    cohort = pd.DataFrame(columns=['Sign up']) 
    cohort['Sign up'] = dates 
    # We will compute the number of retained users, column-by-column 
    #  (There probably is a more pythonesque way of doing it) 
    range_dates = range(0,period_number+1) 
    for p in range_dates: 
     # Name of the column 
     s_p = 'Week '+str(p) 
     cohort[s_p] = cohort.apply(lambda row: retained_in_interval(users,row['Sign up'],p,end_date), axis=1) 

    cohort = cohort.set_index('Sign up')   
    # absolute values to percentage by dividing by the value of week 0 : 
    cohort = cohort.astype('float').div(cohort['Week 0'].astype('float'),axis='index') 
    return cohort 

Ora si può chiamare e vedere il risultato:

cohort_table(users) 

enter image description here

Spero che sia utile

0

Utilizzando lo stesso formato di users dati dalla risposta di rom_j, questo sarà più pulito/più veloce, ma funziona solo supponendo che vi sia almeno una registrazione/abbandono alla settimana. Non una terribile ipotesi su dati abbastanza grandi.

users = users.applymap(lambda d: d.strftime('%Y-%m-%V') if pd.notnull(d) else d) 
tab = pd.crosstab(signup_date, last_log) 
totals = tab.T.sum() 
retention_counts = ((tab.T.cumsum().T * -1) 
        .replace(0, pd.NaT) 
        .add(totals, axis=0) 
        ) 
retention = retention_counts.div(totals, axis=0) 

realined = [retention.loc[a].dropna().values for a in retention.index] 
realigned_retention = pd.DataFrame(realined, index=retention.index) 
Problemi correlati