Análisis de cohortes de clientes de una tienda online¶
Paso 1. Acceda los datos y prepáralos para el análisis¶
# Importar Librerías
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
from scipy import stats
import plotly.express as px
visits = pd.read_csv('visits_log_us.csv')
orders = pd.read_csv('orders_log_us.csv')
costs = pd.read_csv('costs_us.csv')
# Normalizar nombres de columnas
visits.columns = visits.columns.str.strip().str.lower().str.replace(" ", "_")
orders.columns = orders.columns.str.strip().str.lower().str.replace(" ", "_")
costs.columns = costs.columns.str.strip().str.lower().str.replace(" ", "_")
# Asegúrate de que cada columna contenga el tipo de datos correcto.
# Comprobación de tipos antes de procesar
print("\n=== INFO ANTES DE PROCESAR ===\n")
print("visits:", visits.info())
print("orders:", orders.info())
print("costs:", costs.info())
=== INFO ANTES DE PROCESAR === <class 'pandas.core.frame.DataFrame'> RangeIndex: 359400 entries, 0 to 359399 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 device 359400 non-null object 1 end_ts 359400 non-null object 2 source_id 359400 non-null int64 3 start_ts 359400 non-null object 4 uid 359400 non-null uint64 dtypes: int64(1), object(3), uint64(1) memory usage: 13.7+ MB visits: None <class 'pandas.core.frame.DataFrame'> RangeIndex: 50415 entries, 0 to 50414 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 buy_ts 50415 non-null object 1 revenue 50415 non-null float64 2 uid 50415 non-null uint64 dtypes: float64(1), object(1), uint64(1) memory usage: 1.2+ MB orders: None <class 'pandas.core.frame.DataFrame'> RangeIndex: 2542 entries, 0 to 2541 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 source_id 2542 non-null int64 1 dt 2542 non-null object 2 costs 2542 non-null float64 dtypes: float64(1), int64(1), object(1) memory usage: 59.7+ KB costs: None
# Corrección de tipos de fecha/hora necesarios
# costs: columna de fecha
costs['dt'] = pd.to_datetime(costs['dt'], errors='coerce')
# orders: timestamp de compra
orders['buy_ts'] = pd.to_datetime(orders['buy_ts'], errors='coerce')
# visits: timestamps de inicio y fin de la visita
visits['start_ts'] = pd.to_datetime(visits['start_ts'], errors='coerce')
visits['end_ts'] = pd.to_datetime(visits['end_ts'], errors='coerce')
# === Comprobar valores faltantes por columna ===
print("=== Faltantes en visits ===")
print(visits.isna().sum(), "\n")
print("=== Faltantes en orders ===")
print(orders.isna().sum(), "\n")
print("=== Faltantes en costs ===")
print(costs.isna().sum(), "\n")
# === Comprobar número total de duplicados ===
print("=== Duplicados en visits ===")
print(visits.duplicated().sum(), "\n")
print("=== Duplicados en orders ===")
print(orders.duplicated().sum(), "\n")
print("=== Duplicados en costs ===")
print(costs.duplicated().sum(), "\n")
# Comprobación de tipos después de procesar
print("\n=== INFO DESPUÉS DE PROCESAR ===\n")
print("visits.info():")
print(visits.info(), "\n")
print("orders.info():")
print(orders.info(), "\n")
print("costs.info():")
print(costs.info(), "\n")
=== Faltantes en visits === device 0 end_ts 0 source_id 0 start_ts 0 uid 0 dtype: int64 === Faltantes en orders === buy_ts 0 revenue 0 uid 0 dtype: int64 === Faltantes en costs === source_id 0 dt 0 costs 0 dtype: int64 === Duplicados en visits === 0 === Duplicados en orders === 0 === Duplicados en costs === 0 === INFO DESPUÉS DE PROCESAR === visits.info(): <class 'pandas.core.frame.DataFrame'> RangeIndex: 359400 entries, 0 to 359399 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 device 359400 non-null object 1 end_ts 359400 non-null datetime64[ns] 2 source_id 359400 non-null int64 3 start_ts 359400 non-null datetime64[ns] 4 uid 359400 non-null uint64 dtypes: datetime64[ns](2), int64(1), object(1), uint64(1) memory usage: 13.7+ MB None orders.info(): <class 'pandas.core.frame.DataFrame'> RangeIndex: 50415 entries, 0 to 50414 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 buy_ts 50415 non-null datetime64[ns] 1 revenue 50415 non-null float64 2 uid 50415 non-null uint64 dtypes: datetime64[ns](1), float64(1), uint64(1) memory usage: 1.2 MB None costs.info(): <class 'pandas.core.frame.DataFrame'> RangeIndex: 2542 entries, 0 to 2541 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 source_id 2542 non-null int64 1 dt 2542 non-null datetime64[ns] 2 costs 2542 non-null float64 dtypes: datetime64[ns](1), float64(1), int64(1) memory usage: 59.7 KB None
Paso 2. Haz informes y calcula métricas¶
Visitas:
# ¿Cuántas personas lo usan cada día, semana y mes?
visits['session_year'] = visits['start_ts'].dt.isocalendar().year
visits['session_month'] = visits['start_ts'].dt.month
visits['session_week'] = visits['start_ts'].dt.isocalendar().week
visits['session_date'] = visits['start_ts'].dt.date
dau_total = visits.groupby('session_date').agg({'uid': 'nunique'})
wau_total = visits.groupby(['session_year', 'session_week']).agg({'uid': 'nunique'})
mau_total = visits.groupby(['session_year', 'session_month']).agg({'uid': 'nunique'})
wau_index = wau_total.index.to_frame(index=False)
wau_index['session_week'] = wau_index['session_week'].astype(int)
year_str_w = wau_index['session_year'].astype(str)
week_str_w = wau_index['session_week'].astype(str).str.zfill(2)
iso_str_w = year_str_w + '-W' + week_str_w + '-1'
wau_dates = pd.to_datetime(iso_str_w, format='%G-W%V-%u', errors='coerce')
wau_total.index = wau_dates
mau_index = mau_total.index.to_frame(index=False)
mau_index['session_month'] = mau_index['session_month'].astype(int)
year_str_m = mau_index['session_year'].astype(str)
month_str_m = mau_index['session_month'].astype(str).str.zfill(2)
date_str_m = year_str_m + '-' + month_str_m + '-01'
mau_dates = pd.to_datetime(date_str_m, format='%Y-%m-%d', errors='coerce')
mau_total.index = mau_dates
fig, axes = plt.subplots(3, 1, figsize=(12, 15))
# 1. DAU - Usuarios Activos Diarios
axes[0].plot(dau_total.index, dau_total['uid'], linewidth=2)
axes[0].set_title('Usuarios Activos Diarios (DAU)', fontsize=14, fontweight='bold')
axes[0].set_xlabel('Fecha')
axes[0].set_ylabel('Número de Usuarios Únicos')
axes[0].grid(True, alpha=0.3)
axes[0].tick_params(axis='x', rotation=45)
# 2. WAU - Usuarios Activos Semanales
axes[1].plot(wau_total.index, wau_total['uid'], linewidth=2)
axes[1].set_title('Usuarios Activos Semanales (WAU)', fontsize=14, fontweight='bold')
axes[1].set_xlabel('Semana')
axes[1].set_ylabel('Número de Usuarios Únicos')
axes[1].grid(True, alpha=0.3)
axes[1].tick_params(axis='x', rotation=45)
# 3. MAU - Usuarios Activos Mensuales
axes[2].plot(mau_total.index, mau_total['uid'], linewidth=2)
axes[2].set_title('Usuarios Activos Mensuales (MAU)', fontsize=14, fontweight='bold')
axes[2].set_xlabel('Mes')
axes[2].set_ylabel('Número de Usuarios Únicos')
axes[2].grid(True, alpha=0.3)
axes[2].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
# ¿Cuántas sesiones hay por día? (Un usuario puede tener más de una sesión).
sesiones_diarias = visits.groupby('session_date').size()
print(f"Sesiones diarias promedio: {sesiones_diarias.mean():.0f}")
print(f"DAU promedio: {int(dau_total.mean())}")
Sesiones diarias promedio: 987 DAU promedio: 907
/var/folders/nn/8fxj8t5x4w3fy9hxxr41r9sw0000gn/T/ipykernel_7566/621433215.py:6: FutureWarning: Calling int on a single element Series is deprecated and will raise a TypeError in the future. Use int(ser.iloc[0]) instead
print(f"DAU promedio: {int(dau_total.mean())}")
# ¿Cuál es la duración de cada sesión?
visits['session_duration_sec'] = (visits['end_ts'] - visits['start_ts']).dt.total_seconds()
visits['session_duration_min'] = visits['session_duration_sec'] / 60
duracion_promedio = visits['session_duration_min'].mean()
plt.figure(figsize=(12, 6))
plt.hist(visits['session_duration_min'], bins=50, alpha=0.7, color='skyblue', edgecolor='black')
plt.title('Distribución de Duración de Sesiones', fontsize=16, fontweight='bold')
plt.xlabel('Duración (minutos)', fontsize=12)
plt.ylabel('Número de Sesiones', fontsize=12)
plt.axvline(duracion_promedio, color='red', linestyle='--', linewidth=2, label=f'Duración Promedio: {duracion_promedio:.1f} min')
plt.legend(fontsize=20, loc='upper right', frameon=True, fancybox=True, shadow=True)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# ¿Con qué frecuencia los usuarios regresan?
visits_with_duration = visits[visits['session_duration_sec'] > 0]
sesiones_por_usuario = visits_with_duration.groupby('uid').size()
total_usuarios = sesiones_por_usuario.count()
usuarios_unicos = (sesiones_por_usuario == 1).sum()
usuarios_recurrentes = total_usuarios - usuarios_unicos
print(f"Total de usuarios: {total_usuarios:,}")
print(f"Usuarios únicos (1 sesión): {usuarios_unicos:,} ({usuarios_unicos/total_usuarios*100:.1f}%)")
print(f"Usuarios recurrentes (2+ sesiones): {usuarios_recurrentes:,} ({usuarios_recurrentes/total_usuarios*100:.1f}%)")
Total de usuarios: 207,051 Usuarios únicos (1 sesión): 160,499 (77.5%) Usuarios recurrentes (2+ sesiones): 46,552 (22.5%)
Ventas:
# ¿Cuándo empieza la gente a comprar?
first_visit = visits.groupby('uid')['start_ts'].min().reset_index()
first_visit.columns = ['uid', 'first_visit_date']
first_order = orders.groupby('uid')['buy_ts'].min().reset_index()
first_order.columns = ['uid', 'first_order_date']
conversion_data = first_visit.merge(first_order, on='uid', how='inner')
conversion_data['conversion_time'] = conversion_data['first_order_date'] - conversion_data['first_visit_date']
conversion_data['days_to_convert'] = conversion_data['conversion_time'].dt.days
conversion_data['conversion_group'] = np.where(
conversion_data['days_to_convert'] == 0, 'Conversión inmediata',
np.where(
(conversion_data['days_to_convert'] >= 1) & (conversion_data['days_to_convert'] <= 7), 'Conversión Rapida',
np.where(
(conversion_data['days_to_convert'] >= 8) & (conversion_data['days_to_convert'] <= 30), 'Conversión Media',
'Conversión Lenta'
)
)
)
conversion_groups = conversion_data.groupby('conversion_group')['uid'].count()
order = ['Conversión inmediata', 'Conversión Rapida', 'Conversión Media', 'Conversión Lenta']
conversion_groups = conversion_groups.reindex(order)
group_ranges = {
'Conversión inmediata': '(0 días)',
'Conversión Rapida': '(1-7 días)',
'Conversión Media': '(8-30 días)',
'Conversión Lenta': '(31+ días)'
}
labels_with_ranges = [f"{group} {group_ranges[group]}" for group in conversion_groups.index]
avg_conversion_time = conversion_data['days_to_convert'].mean()
print(f"Tiempo promedio entre el primer registro y la primera compra: {avg_conversion_time:.1f} días")
first_visit_with_source = visits.merge(
first_visit,
left_on=['uid', 'start_ts'],
right_on=['uid', 'first_visit_date'],
how='inner'
)[['uid', 'source_id']]
conversion_analysis = conversion_data.merge(first_visit_with_source, on='uid', how='left')
conversions_by_source = conversion_analysis.groupby('source_id').agg({
'uid': 'count',
'days_to_convert': 'mean'
}).round(1)
conversions_by_source.columns = ['total_conversions', 'avg_days_to_convert']
conversions_by_source = conversions_by_source.sort_values('total_conversions', ascending=False)
top_sources = conversions_by_source.head(8)
other_conversions = conversions_by_source.iloc[8:]['total_conversions'].sum()
if other_conversions > 0:
pie_data = list(top_sources['total_conversions']) + [other_conversions]
pie_labels = [f'Fuente {idx}' for idx in top_sources.index] + ['Otras fuentes']
else:
pie_data = list(top_sources['total_conversions'])
pie_labels = [f'Fuente {idx}' for idx in top_sources.index]
colors = plt.cm.Set3(np.linspace(0, 1, len(pie_data)))
explode = [0.05 if i < 3 else 0 for i in range(len(pie_data))]
total_conversions = conversions_by_source['total_conversions'].sum()
top3_pct = top_sources.head(3)['total_conversions'].sum() / total_conversions * 100
print(f"\nTotal de conversiones: {total_conversions:,}")
print(f"Top 3 fuentes representan: {top3_pct:.1f}% del total")
fig, axes = plt.subplots(1, 2, figsize=(18, 8))
bars = axes[0].bar(
range(len(conversion_groups)),
conversion_groups.values,
color=['#2E8B57', '#4682B4', '#DAA520', '#CD5C5C']
)
axes[0].set_title('Comparación de Tiempos de Conversión', fontsize=16, fontweight='bold')
axes[0].set_xlabel('Grupo de Conversión', fontsize=12)
axes[0].set_ylabel('Número de Usuarios', fontsize=12)
axes[0].set_xticks(range(len(conversion_groups)))
axes[0].set_xticklabels(labels_with_ranges, rotation=45, ha='right')
for i, bar in enumerate(bars):
height = bar.get_height()
axes[0].text(
bar.get_x() + bar.get_width()/2., height + 200,
f'{int(height):,} usuarios',
ha='center', va='bottom',
fontsize=10, fontweight='bold'
)
axes[0].grid(True, alpha=0.3, axis='y')
wedges, texts, autotexts = axes[1].pie(
pie_data,
labels=pie_labels,
autopct='%1.1f%%',
startangle=90,
colors=colors,
explode=explode
)
for autotext in autotexts:
autotext.set_color('black')
autotext.set_fontweight('bold')
autotext.set_fontsize(10)
axes[1].set_title(
'Distribución de Conversiones por Fuente de Anuncios',
fontsize=16, fontweight='bold', pad=20
)
axes[1].axis('equal')
plt.tight_layout()
plt.show()
Tiempo promedio entre el primer registro y la primera compra: 16.7 días Total de conversiones: 36,523 Top 3 fuentes representan: 75.8% del total
# ¿Cuántos pedidos hacen durante un período de tiempo dado?
orders_per_month = orders.groupby(orders['buy_ts'].dt.to_period('M'))['uid'].count()
plt.figure(figsize=(12, 6))
plt.plot(orders_per_month.index.astype(str), orders_per_month.values,
marker='o', linewidth=2, markersize=8)
plt.title('Pedidos por Mes', fontsize=16, fontweight='bold')
plt.xlabel('Mes', fontsize=12)
plt.ylabel('Número de Pedidos', fontsize=12)
plt.xticks(rotation=45)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print(f"Promedio mensual de pedidos: {orders_per_month.mean():.0f}")
print(f"Mes con más pedidos: {orders_per_month.idxmax()} ({orders_per_month.max():,} pedidos)")
Promedio mensual de pedidos: 3878 Mes con más pedidos: 2017-12 (6,218 pedidos)
# ¿Cuál es el tamaño promedio de compra?
avg_revenue_per_order = orders.groupby(orders['buy_ts'].dt.to_period('M'))['revenue'].mean()
plt.figure(figsize=(12, 6))
plt.plot(avg_revenue_per_order.index.astype(str), avg_revenue_per_order.values,
marker='o', linewidth=2, markersize=8, color='green')
plt.title('Tamaño Promedio de Compra', fontsize=16, fontweight='bold')
plt.ylabel('Revenue Promedio ($)', fontsize=12)
plt.xticks(rotation=45)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# ¿Cuánto dinero traen? (LTV)
ltv_por_usuario = orders.groupby('uid').agg(
ltv=('revenue', 'sum'),
compras=('revenue', 'count'),
ticket_promedio=('revenue', 'mean')
).sort_values('ltv', ascending=False)
ltv_promedio = ltv_por_usuario['ltv'].mean()
print(f"LTV promedio: ${ltv_promedio:,.2f}")
ltv_con_cohorte = orders.merge(first_visit, on='uid', how='left')
ltv_con_cohorte['cohorte'] = ltv_con_cohorte['first_visit_date'].dt.to_period('M')
ltv_por_cohorte = (
ltv_con_cohorte
.groupby('cohorte')['revenue']
.sum()
.sort_index()
)
cohort_dates = ltv_por_cohorte.index.to_timestamp()
plt.figure(figsize=(12, 6))
plt.plot(
cohort_dates,
ltv_por_cohorte.values,
linewidth=3,
marker='o',
markersize=8,
)
for x, y in zip(cohort_dates, ltv_por_cohorte.values):
etiqueta = f"${y:,.0f}"
plt.text(
x,
y + (y * 0.02),
etiqueta,
ha='center', va='bottom',
fontsize=9
)
plt.title('LTV por Cohorte de Adquisición', fontsize=14, fontweight='bold')
plt.xlabel('Mes de Cohorte')
plt.ylabel('Ingresos Acumulados')
plt.grid(alpha=0.3)
plt.xticks(rotation=45)
plt.show()
Marketing:
# ¿Cuánto dinero se gastó? (Total/por fuente de adquisición/a lo largo del tiempo)
gasto_total = costs['costs'].sum()
print(f"Gasto total en marketing: ${gasto_total:,.2f}")
# Gasto por fuente de marketing
gasto_por_fuente = costs.groupby('source_id')['costs'].sum().sort_values(ascending=False)
# Gasto por mes
gasto_por_mes = costs.groupby(costs['dt'].dt.to_period('M'))['costs'].sum()
gasto_por_mes.index = gasto_por_mes.index.to_timestamp()
fig, axes = plt.subplots(1, 2, figsize=(18, 6))
axes[0].pie(
gasto_por_fuente,
labels=gasto_por_fuente.index,
autopct='%1.1f%%',
startangle=90,
counterclock=False
)
axes[0].set_title('Gasto por Fuente de Marketing', fontsize=14, fontweight='bold')
axes[1].bar(
gasto_por_mes.index.strftime('%Y-%m'),
gasto_por_mes.values
)
axes[1].set_title('Gasto Mensual en Marketing', fontsize=14, fontweight='bold')
axes[1].set_xlabel('Mes')
axes[1].set_ylabel('Gasto ($)')
axes[1].tick_params(axis='x', rotation=45)
axes[1].grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
Gasto total en marketing: $329,131.62
# ¿Cuál fue el costo de adquisición de clientes de cada una de las fuentes?
first_source = (
visits.sort_values('start_ts')
.groupby('uid', as_index=False)['source_id']
.first()
)
buyers = orders[['uid']].drop_duplicates()
buyers_with_source = buyers.merge(first_source, on='uid', how='left')
clientes_por_fuente = (
buyers_with_source.groupby('source_id')['uid']
.nunique()
.rename('n_clientes')
)
gasto_por_fuente = costs.groupby('source_id')['costs'].sum()
cac_por_fuente = gasto_por_fuente.to_frame().join(clientes_por_fuente, how='left')
cac_por_fuente['cac'] = cac_por_fuente['costs'] / cac_por_fuente['n_clientes']
fig, ax = plt.subplots(figsize=(10, 6))
ax.bar(cac_por_fuente.index.astype(str), cac_por_fuente['cac'])
ax.set_title('Costo de Adquisición de Clientes (CAC) por Fuente', fontsize=14, fontweight='bold')
ax.set_xlabel('Fuente de adquisición (source_id)')
ax.set_ylabel('CAC ($ por cliente)')
ax.grid(axis='y', alpha=0.3)
plt.xticks(rotation=45)
for i, v in enumerate(cac_por_fuente['cac']):
ax.text(i, v + (v * 0.01), f'${v:.2f}', ha='center', va='bottom')
plt.tight_layout()
plt.show()
# ¿Cuán rentables eran las inversiones? (ROMI)
ingresos_por_usuario = orders.groupby('uid')['revenue'].sum().reset_index()
ingresos_con_fuente = ingresos_por_usuario.merge(first_source, on='uid', how='left')
ingresos_por_fuente = ingresos_con_fuente.groupby('source_id')['revenue'].sum()
costo_por_fuente = costs.groupby('source_id')['costs'].sum()
romi = (ingresos_por_fuente - costo_por_fuente) / costo_por_fuente
romi_df = romi.to_frame(name='romi').sort_index()
plt.figure(figsize=(10, 6))
plt.bar(romi_df.index.astype(str), romi_df['romi'], color='skyblue')
plt.title('ROMI por Fuente de Adquisición', fontsize=14, fontweight='bold')
plt.xlabel('Fuente (source_id)')
plt.ylabel('ROMI (retorno por peso invertido)')
plt.grid(axis='y', alpha=0.3)
plt.xticks(rotation=45)
plt.axhline(0, color='red', linewidth=2)
plt.tight_layout()
plt.show()
resumen_romi = pd.DataFrame({
'Ingresos': ingresos_por_fuente,
'Costos': costo_por_fuente,
'ROMI': romi
}).fillna(0)
resumen_romi['Rentable'] = resumen_romi['ROMI'] > 0
print(resumen_romi.round(2))
Ingresos Costos ROMI Rentable source_id 1 31090.55 20833.27 0.49 True 2 46923.61 42806.04 0.10 True 3 54511.24 141321.63 -0.61 False 4 56696.83 61073.60 -0.07 False 5 52624.02 51757.10 0.02 True 7 1.22 0.00 0.00 False 9 5759.40 5517.49 0.04 True 10 4450.33 5822.49 -0.24 False
Paso 3. Escribe una conclusión: aconseja a los expertos de marketing cuánto dinero invertir y dónde¶
Después de analizar el desempeño de cada fuente de adquisición a partir de las métricas de Ingresos, Costos, y especialmente ROMI, observamos un patrón claro: solo algunas fuentes generan resultados positivos contribuyendo a la adquisición de clientes, mientras que otras generan retornos despreciables o directamente no son rentables.
Fuentes recomendables para invertir según el analisis:
Fuente 1:
- ROMI = 0.49 (49% de retorno)
- Ingresos: 31,090
- Costos: 20,833
Es la fuente más rentable. Por cada peso invertido devuelve casi medio peso adicional. Además, su relación ingreso/costo es estable y supera por mucho a todas las demás.
- Fuente 2:
- ROMI = 0.10 (10% de retorno)
- Ingresos: 46,923
- Costos: 42,806
Ligero retorno positivo, apenas por encima del punto de equilibrio. Aunque no es explosivamente rentable, no está destruyendo valor, lo cual la vuelve una fuente segura con potencial de optimización.
- Fuente 9:
- ROMI = 0.04 (4% de retorno)
- Ingresos: 5,759
- Costos: 5,517
Solo genera un pequeño retorno, pero no genera pérdida, por lo que es una fuente estable de bajo riesgo.
- Fuente 5
- ROMI = 0.02 (2% de retorno)
- Ingresos: 52,624
- Costos: 51,757
Genera ingresos casi iguales a su costo. Aunque la rentabilidad es baja, no genera pérdidas, por lo que puede mantenerse si se mejora el targeting o el formato de campaña.
Fuentes no recomendables para invertir según el analisis:
- Fuente 3
- ROMI = –0.61
- Ingresos: 54,511
- Costos: 141,321
La peor fuente del portafolio. Por cada 1 peso invertido se pierde más de medio peso.
- Fuente 10
- ROMI = –0.24
- Ingresos: 4,450
- Costos: 5,822
Pequeño volumen, pero mal desempeño.
- Fuente 4
- ROMI = –0.07
- Ingresos: 56,696
- Costos: 61,073
No es tan perjudicial como la fuente 3, pero aún es negativa.
En conclusión, el análisis de ROMI, ingresos y costos por fuente muestra que solo algunas plataformas generan verdadero retorno y deben recibir la mayor parte del presupuesto de marketing. Las fuentes 1 y 2 destacan como las más rentables, con retornos positivos que justifican aumentar la inversión en ellas, mientras que las fuentes 5 y 9 muestran resultados ligeramente positivos y pueden mantenerse con vigilancia, buscando optimización para mejorar su desempeño. En contraste, las fuentes 3, 4 y 10 presentan ROMI negativo y están destruyendo valor, por lo que se recomienda reducir drásticamente o pausar la inversión hasta corregir fallas en segmentación o eficiencia. Finalmente, la fuente 7 no aporta valor medible y no debe recibir presupuesto adicional hasta entender su rol. Esta estrategia asegura que los recursos se concentren en las fuentes que realmente impulsan crecimiento y rentabilidad, evitando pérdidas y maximizando el retorno total de las campañas.