import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import scipy.stats as stats
visits = pd.read_csv('/datasets/visits_log_us.csv')
orders = pd.read_csv('/datasets/orders_log_us.csv')
cost = pd.read_csv('/datasets/costs_us.csv')
visits.info()
visits.head()
orders.info()
orders.head()
cost.info()
cost.head()
#normalizaos los nombres de las columnas
visits.columns = visits.columns.str.lower().str.replace(' ', '_')
orders.columns = orders.columns.str.lower().str.replace(' ', '_')
cost.columns = cost.columns.str.lower().str.replace(' ', '_')
orders.info()
#convertimos las columnas que son fechas a tipo fechas
visits['end_ts'] = pd.to_datetime(visits['end_ts'])
visits['start_ts'] = pd.to_datetime(visits['start_ts'])
orders['buy_ts'] = pd.to_datetime(orders['buy_ts'])
cost['dt'] = pd.to_datetime(cost['dt'])
visits.info()
orders.info()
# Extraemos el mes,año y semana de la columna start_ts
visits['date'] = visits['start_ts'].dt.date
visits['start_ts_month'] = visits['start_ts'].dt.month
visits['start_ts_year'] = visits['start_ts'].dt.isocalendar().year
visits['start_ts_week'] = visits['start_ts'].dt.isocalendar().week
visits.head()
# Contamos la cantidad de visitas por semana
visits_weekly = visits.groupby(['start_ts_year', 'start_ts_week']).size().reset_index(name='visit_count')
print(visits_weekly.head(10))
print(visits_weekly['visit_count'].mean())
# Contamos visitas por mes
visits_monthly = visits.groupby(['start_ts_year', 'start_ts_month']).size().reset_index(name='visit_count')
print(visits_monthly.head(10))
print(visits_monthly['visit_count'].mean())
# Contar visitas por dia
visits_daily = visits.groupby('date').size().reset_index(name='visit_count')
print(visits_daily.head(10))
print(visits_daily['visit_count'].mean())
#Cuantas personas por dia
users_per_day = visits.groupby(visits['start_ts'].dt.date)['uid'].nunique().reset_index()
users_per_day.columns = ['date', 'n_users']
print(users_per_day.head(10))
print(users_per_day['n_users'].mean())
#Cuantas personas por semana
users_per_week = visits.groupby(['start_ts_year', 'start_ts_week'])['uid'].nunique().reset_index()
users_per_week.columns = ['year','week' ,'n_users']
print(users_per_week.head(10))
print(users_per_week['n_users'].mean())
#Cuantas personas por mes
users_per_monthly = visits.groupby(['start_ts_year', 'start_ts_month'])['uid'].nunique().reset_index()
users_per_monthly.columns = ['year','month' ,'n_users']
print(users_per_monthly.head(10))
print(users_per_monthly['n_users'].mean())
#calculamos la duracion de cada sesion
visits['duration_minutes'] = (visits['end_ts'] - visits['start_ts']) / np.timedelta64(1, 'm')
print(visits.head(10))
#Graficamos la distribucion
visits['duration_minutes'].hist(bins = 50)
#Vemos si hay algun valor que se repite demasiado
visits['duration_minutes'].value_counts().sort_values(ascending = False)
#Imprimimos a moda mediana ya qeu la media ni la moda son valores representativos
print(visits['duration_minutes'].median())
#Calculamos la frecuencia en la que los usuarion regresan por semana
frecuencia_regreso_week = visits.groupby(['uid', 'start_ts_year', 'start_ts_week']).agg(n_sesiones=('uid', 'count')).reset_index()
frecuencia_regreso_week.columns = ['uid', 'start_ts_year', 'start_ts_week', 'n_sesiones']
print(frecuencia_regreso_week.sort_values(by='n_sesiones', ascending=False).head(20))
#Calculamos la frecuencia en la que los usuarion regresan por mes
frecuencia_regreso_month = visits.groupby(['uid', 'start_ts_year', 'start_ts_month']).agg(n_sesiones=('uid', 'count')).reset_index()
frecuencia_regreso_month.columns = ['uid', 'start_ts_year', 'start_ts_month', 'n_sesiones']
print(frecuencia_regreso_month.sort_values(by='n_sesiones', ascending=False).head(20))
# vemos si existe una distribucion normal el la frecuencia de regreso semanal
sns.histplot(frecuencia_regreso_week['n_sesiones'], kde=True)
plt.title('Distribución de n_sesiones')
plt.xlabel('n_sesiones')
plt.ylabel('Frecuencia')
plt.show()
# vemos si existe una distribucion normal en la frecuencia de regreso mensual
stats.probplot(frecuencia_regreso_month['n_sesiones'], dist="norm", plot=plt)
plt.show()
frecuencia_regreso_week.describe()
print(frecuencia_regreso_week['n_sesiones'].mode())
#calculamos el tiempo que transcurre desde que inicia sesion por primera vez hasta qeu realiza la primera compra,para eso combinmos los df de visits con orders
orders.head()
orders['date'] = orders['buy_ts'].dt.date
print(orders.head())
print(visits)
#combinamos ambos df para tener los tiempos de inicio de sesión y de compra
df_merged = orders.merge(
visits[['uid', 'date', 'start_ts']],
on=['uid', 'date'],
how='left'
)
print(df_merged.head())
df_merged.info()
# Convertimos la columna date a tipo dato
df_merged['date'] = pd.to_datetime(df_merged['date'])
df_merged.info()
df_merged.isna().sum()
#limpiamos los nulos
df_merged_clean = df_merged.dropna()
df_merged_clean.isna().sum()
#calculamos el tiempo trancurrido entre la primera sesion y la primera compra
first_buy = df_merged_clean.groupby(['uid'])['start_ts'].min()
first_buy.name = 'first_session'
df_merged_first_session = df_merged_clean.join(first_buy, on = 'uid')
first_compra = df_merged_clean.groupby(['uid'])['buy_ts'].min()
first_compra.name = 'first_compra'
df_merged_first_session_1 = df_merged_first_session.join(first_compra, on = 'uid')
print(df_merged_first_session_1.head())
#calculamos el tiempo en que se tarda hacer una compra
df_merged_first_session_1['tiempo_primera_compra_min'] = (df_merged_first_session_1['first_compra']-df_merged_first_session_1['first_session'])/np.timedelta64(1,'m')
df_merged_first_session_1.head(20)
df_merged_first_session_1.describe()
#vemos si representa una distribucion normal,sin tomar en cuenta los numeos negativos
stats.probplot(df_merged_first_session_1['tiempo_primera_compra_min']>0, dist="norm", plot=plt)
plt.show()
#Ya que no existe una distribución normal para poder tomar la media como medida representativa,vemos si existe algún valor que se repite
df_merged_first_session_1['tiempo_primera_compra_min'].value_counts().head(15)
df_merged_first_session_1['tiempo_primera_compra_min'].median()
#Calcularemos la cantidad de pedidos que realiza cada cliente deacuerdo a su ciclo de vida,es decir los pedidos que hacen en su primer mes,segundo mes y asi sustantivamente
orders.info()
orders['month_year'] = orders['buy_ts'].dt.to_period('M')
orders.head()
orders['month'] = orders['buy_ts'].dt.month
orders['week'] = orders['buy_ts'].dt.isocalendar().week
orders['year'] = orders['buy_ts'].dt.isocalendar().year
orders.head()
# calculamos el ciclo de vida del cliente desde su primera compra
first_orders = orders.groupby('uid')['date'].min().reset_index()
first_orders.columns = ['uid','first_order_date']
first_orders.head()
first_orders['first_month'] = first_orders['first_order_date'].astype('datetime64[M]').dt.to_period('M')
first_orders.info()
first_orders.head()
orders_1 = orders.merge(first_orders,on = 'uid',how = 'left')
orders_1.head()
#calculamos el ciclo de vida del cliente
orders_1['age']= (orders_1['date']-orders_1['first_order_date'])/np.timedelta64(1,'M')
orders_1['age'] = orders_1['age'].round().astype('int')
orders_1.head()
#calculamos el numero de pedidos,cantidad de compradores ,la ganancia total por edad de los clientes y el tamano promedio de compra
pedidos_por_tiempo = orders_1.groupby('age').agg(numero_pedidos = ('buy_ts','count'),n_buyers = ('uid','nunique'),revenue= ('revenue','sum'),revenue_prom = ('revenue','mean')).reset_index()
pedidos_por_tiempo['pedidos_prom'] = pedidos_por_tiempo['numero_pedidos']/pedidos_por_tiempo['n_buyers']
pedidos_por_tiempo['retencion_pct'] = pedidos_por_tiempo['n_buyers'].pct_change() * 100
pedidos_por_tiempo.head(15)
#Para comparar sacamos en tamano promedio de compra a nivel general
tamaño_promedio = orders['revenue'].mean()
print("Tamaño promedio de compra:", tamaño_promedio)
#Calculamos el ltv para los clientes nuevos que se adquiere cada mes
clientes_nuevos = orders_1[orders_1['date'] == orders_1['first_order_date']]
clientes_nuevos.head()
# Ordenar por fecha de compra
orders_sorted = orders_1.sort_values(by=['uid', 'date'])
# Filtrar la primera compra de cada usuario
clientes_nuevos = orders_sorted.drop_duplicates(subset='uid', keep='first')
clientes_nuevos.head()
clientes_nuevos.info()
clientes_nuevos = clientes_nuevos.drop_duplicates()
clientes_nuevos['uid'].nunique()
revenue_por_usuario = clientes_nuevos.groupby(['month_year','uid'])['revenue'].sum().reset_index()
revenue_por_usuario.columns = ['month_year','uid','revenue']
revenue_por_usuario.head()
ltv = revenue_por_usuario.groupby('month_year')['revenue'].mean().reset_index()
ltv.columns = ['month_year','LTV']
ltv.head(15)
ltv['LTV'].mean()
#anadimos una columna donde muestres solo el mes y año
cost['month_year'] = cost['dt'].dt.to_period('M')
cost.head()
total_source_month = cost.groupby(['month_year','source_id'])['costs'].sum().reset_index()
total_source_month.columns = ['month_year','source_id','cost']
total_source_month.head(10)
plt.figure(figsize=(14, 6))
sns.barplot(data=total_source_month, x='month_year', y='cost', hue='source_id')
plt.title('Costo mensual por fuente de adquisición')
plt.xlabel('Mes')
plt.ylabel('Costo ($)')
plt.xticks(rotation=45)
plt.legend(title='source_id', bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
plt.show()
#Unimos el df de clientes nuevos con visits para obtener el source_id de cada cliente
clientes_nuevos_source = clientes_nuevos.merge(visits[['uid','source_id']],on ='uid',how = 'left')
clientes_nuevos_source.head()
#calculamos la cantidad de compradores y la ganancia por mes y source
revenue_month_source = clientes_nuevos_source.groupby(['source_id','month_year']).agg(n_buyers = ('uid','count'),revenue = ('revenue','sum')) .reset_index()
revenue_month_source.head(20)
cost.head()
cost_month = cost.groupby(['source_id','month_year'])['costs'].sum().reset_index()
cost_month.head(20)
#juntamos ambos df para obtener los gstos por fuente y mes
revenue_month_source = revenue_month_source.merge(cost_month[['source_id','month_year','costs']],on =['source_id','month_year'],how ='left')
revenue_month_source.head()
#calculamos el CAC
revenue_month_source['CAC'] = revenue_month_source['costs']/revenue_month_source['n_buyers']
revenue_month_source.head()
#calculamos el ROMI
revenue_month_source['ROMI'] = revenue_month_source['LTV']/revenue_month_source['CAC']
revenue_month_source['diferencia'] = revenue_month_source['LTV'] - revenue_month_source['CAC']
revenue_month_source.head()
palette = sns.color_palette("tab10",8)
plt.figure(figsize=(12, 6))
sns.lineplot(
data=revenue_month_source,
x='month_year',
y='diferencia',
hue='source_id',
marker='o',
palette = palette
)
# Personalización
plt.title('Evolución de LTV - CAC por fuente')
plt.xlabel('Mes')
plt.ylabel('LTV - CAC')
plt.xticks(rotation=45)
plt.tight_layout()
plt.axhline(0, color='red', linestyle='--', linewidth=1.5, label='LTV = CAC')
plt.show()
#Vemos el LTV de la fuente 3, para calcular cuanto falta para poder llegar al equilibrio y no exista pérdidas
revenue_month_source[revenue_month_source['source_id'] == 3]
#vemos que fuente trae mas ganancias
df_grouped = revenue_month_source.groupby('source_id', as_index=False).sum(numeric_only=True)
df_grouped.sort_values(by ='revenue', ascending = False).head(10)
revenue_month_source['diferencia'] = revenue_month_source['LTV'] - revenue_month_source['CAC']
heatmap_data = revenue_month_source.pivot(index='month_year', columns='source_id', values='diferencia')
plt.figure(figsize=(12, 6))
sns.heatmap(
heatmap_data,
annot=True,
fmt=".1f",
cmap="RdYlGn", # verde = positivo (rentable), rojo = negativo (pérdida)
center=0, # rojo por debajo de 0
cbar_kws={'label': 'LTV - CAC'}
)
plt.title("Mapa de calor: diferencia LTV - CAC por fuente y mes")
plt.xlabel("Fuente de adquisición")
plt.ylabel("Mes de adquisición")
plt.tight_layout()
plt.show()
heatmap_cac = revenue_month_source.pivot(index='month_year', columns='source_id', values='CAC')
plt.figure(figsize=(12, 6))
sns.heatmap(
heatmap_cac,
annot=True,
fmt=".1f",
cmap="YlOrRd", # amarillo → rojo (más alto = más caro)
cbar_kws={'label': 'CAC ($/cliente)'}
)
plt.title("Mapa de calor: Costo de adquisición de clientes (CAC)")
plt.xlabel("Fuente de adquisición (source_id)")
plt.ylabel("Mes de adquisición")
plt.tight_layout()
plt.show()
visits.head()
#Graficamos la cantidad de visitas por device a lo largo del tiempo
visits['month_year'] = visits['start_ts'].dt.to_period('M')
device_visits = visits.groupby(['month_year','device'])['start_ts'].count().reset_index()
device_visits.head()
plt.figure(figsize=(10, 5))
sns.barplot(data=device_visits, x='month_year', y='start_ts', hue='device')
plt.title("Visitas mensuales por dispositivo")
plt.ylabel("Visitas")
plt.xlabel("Mes")
plt.tight_layout()
plt.show()
#Graficamos la cantidad de visitas por fuente a lo largo del tiempo
source_visits = visits.groupby(['month_year','source_id'])['start_ts'].count().reset_index()
source_visits['month_year'] = source_visits['month_year'].dt.to_timestamp()
source_visits.head(15)
source_visits.info()
palette = sns.color_palette("tab10",9)
plt.figure(figsize=(10, 6))
sns.lineplot(
data=source_visits,
x='month_year',
y='start_ts',
hue='source_id',
palette=palette,
marker='o'
)
plt.title("Visitas mensuales por fuente")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()