2015-02-26 2 views
5

У меня есть панд dataframe, который выглядит следующим образом:Создание удержания когорты из панда dataframe

+-----------+------------------+---------------+------------+ 
| 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 | 
+-----------+------------------+---------------+------------+ 

Это по существу визит журнал сорта, как это имеет все необходимые данные для создания когортного анализа.

Каждая неделя регистрации - это когорта. Чтобы узнать, сколько человек входит в когорту я могу использовать:

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

То, что я хочу сделать, это создать сводную таблицу с регистрационными недель в качестве ключей. Столбцы должны быть visit_weeks, а значениями должны быть количество уникальных идентификаторов учетных записей, которые имеют более 0 недельных посещений.

Вместе с полными учетными записями в каждой когорте я буду показывать процентные значения вместо абсолютных значений.

Конечный продукт будет выглядеть примерно так:

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

Я попытался поворачиванием dataframe так:

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

Но я не прибит стоимость части. Мне нужно как-то подсчитать идентификатор счета и разделить сумму на агрегирование недели регистрации.

Я новичок в пандах, поэтому, если это не лучший способ удержания когорты, пожалуйста, просветите меня!

Thanks

+0

Можете ли вы вставить образец вашего DataFrame в действительную таблицу HTML? Это позволит другим читать его в пандах, чтобы ответить на их ответы на ваш вопрос. –

ответ

9

В вашем вопросе есть несколько аспектов.

Что вы можете построить с данными у вас есть

Есть several kinds of retention. Для простоты мы упомянем только два:

  • Удержание дня-дня: если пользователь зарегистрировался в день 0, она зарегистрировалась в день N? (Вход в день N + 1 не влияет на этот показатель). Чтобы измерить его, вам необходимо отслеживать все журналы ваших пользователей.
  • Поддержание подвижного состава: если пользователь зарегистрировался в день 0, она входила в систему в день N или через день после этого? (Вход в день N + 1 влияет на этот показатель). Чтобы измерить это, вам нужны только последние журналы ведения ваших пользователей.

Если я правильно понимаю вашу таблицу, у вас есть две релевантные переменные для создания вашей группы лиц: дата регистрации и последний журнал (неделя посещения). Количество еженедельных визитов кажется несущественным.

Таким образом, вы можете перейти только с опцией 2, удержанием прокатки.

Как построить таблицу

Первый, давайте строить фиктивные данные, установленные таким образом, что мы имеем достаточно, чтобы работать, и вы можете воспроизвести его:

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))) 

Так что теперь мы должны иметь что-то который выглядит следующим образом:

users.head() 

enter image description here

Вот код, чтобы создать когорты таблицу:

### 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)))) 
         ]) 

С этим мы можем создать реальную функцию:

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 

Теперь вы можете вызвать его и увидеть результат:

cohort_table(users) 

enter image description here

Надеюсь, это поможет

0

Используя тот же формат users данных от ответа rom_j, это будет более чистым/быстрым, но работает только при условии, что есть хотя бы одна регистрация/отток в неделю. Не страшное предположение о достаточно больших данных.

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) 
Смежные вопросы