Skip to content

Instantly share code, notes, and snippets.

@MikyPo
Created November 14, 2025 11:19
Show Gist options
  • Select an option

  • Save MikyPo/e4c19281c5612251e165f1dd5f62b9d0 to your computer and use it in GitHub Desktop.

Select an option

Save MikyPo/e4c19281c5612251e165f1dd5f62b9d0 to your computer and use it in GitHub Desktop.
rfm_analytics
# Developed by MikyPo
# More code for DA here: https://dzen.ru/mikypo
# Импорт библиотек
import pandas as pd
import numpy as np
import openpyxl
import plotly.express as px
import plotly.graph_objects as go
import warnings
warnings.filterwarnings('ignore')
from plotly.subplots import make_subplots
from datetime import datetime
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.formatting.rule import ColorScaleRule
# Чтение файла
df = pd.read_excel('sales.xlsx')
df.info()
# date_ship (datetime64[ns]) - дата отгрузки
# id_head (object) - идентификатор клиента
# dir (object) - направление бизнеса
# quantity (float64) - количество отгруженного товара
# ship_sum (float64) - стоимость отгруженного товара
# Класс RFMAnalysis
class RFMAnalysis:
def __init__(self, df, analysis_date='2025-11-11'):
"""
Инициализация RFM анализа
Parameters:
df - DataFrame с данными
analysis_date - дата анализа (по умолчанию 2025-11-11)
"""
self.df = df.copy()
self.analysis_date = pd.to_datetime(analysis_date)
self.rfm_data = None
self.all_scores = {}
self.all_segment_data = {}
def prepare_data(self, direction=None):
"""Подготовка данных для анализа"""
# Фильтрация по дате. Период анализа данных
mask = (self.df['date_ship'] >= '2024-01-01') & (self.df['date_ship'] <= self.analysis_date)
df_filtered = self.df[mask].copy()
# Фильтрация по направлению бизнеса если указано.
if direction:
df_filtered = df_filtered[df_filtered['dir'] == direction]
# Для Recency и Frequency используем только ship_sum > 0 чтобы не учитывать корректировки бухгалтерии, т.к. это не отгрузки
df_rf = df_filtered[df_filtered['ship_sum'] > 0].copy()
return df_filtered, df_rf
def calculate_rfm_metrics(self, direction=None):
"""Расчёт RFM метрик"""
df_filtered, df_rf = self.prepare_data(direction)
# Recency: дни с последней покупки
recency_data = df_rf.groupby('id_head')['date_ship'].max().apply(
lambda x: (self.analysis_date - x).days
)
# Frequency: количество уникальных дней с покупками
frequency_data = df_rf.groupby('id_head')['date_ship'].nunique()
# Monetary: общая сумма всех покупок (из всех данных, также с корректировками бухгалтерии)
monetary_data = df_filtered.groupby('id_head')['ship_sum'].sum()
# Собираем все метрики в один DataFrame и сбрасываем индекс, чтобы id_head стал колонкой
rfm_metrics = pd.DataFrame({
'recency': recency_data,
'frequency': frequency_data,
'monetary': monetary_data
}).reset_index()
# Заполнение пропусков нулями для клиентов, у которых нет покупок с ship_sum > 0
rfm_metrics = rfm_metrics.fillna({'recency': 999, 'frequency': 0, 'monetary': 0})
return rfm_metrics
def calculate_scores(self, rfm_metrics):
"""Расчет баллов R, F, M"""
scores = rfm_metrics.copy()
# Убедимся, что нет нулевых или отрицательных значений
scores = scores[scores['frequency'] >= 0]
scores = scores[scores['monetary'] >= 0]
# Recency: 1-лучшие (недавно), 2-средние, 3-худшие (давно)
if len(scores) > 0:
recency_33 = np.percentile(scores['recency'], 33)
recency_67 = np.percentile(scores['recency'], 67)
conditions_recency = [
scores['recency'] <= recency_33,
(scores['recency'] > recency_33) & (scores['recency'] <= recency_67),
scores['recency'] > recency_67
]
choices_recency = [1, 2, 3]
scores['R'] = np.select(conditions_recency, choices_recency, default=3)
else:
scores['R'] = 3
# Frequency: 1-худшие (редко), 2-средние, 3-лучшие (часто)
if len(scores) > 0:
frequency_33 = np.percentile(scores['frequency'], 33)
frequency_67 = np.percentile(scores['frequency'], 67)
conditions_frequency = [
scores['frequency'] <= frequency_33,
(scores['frequency'] > frequency_33) & (scores['frequency'] <= frequency_67),
scores['frequency'] > frequency_67
]
choices_frequency = [1, 2, 3]
scores['F'] = np.select(conditions_frequency, choices_frequency, default=1)
else:
scores['F'] = 1
# Monetary: 1-худшие (мало), 2-средние, 3-лучшие (много)
if len(scores) > 0:
monetary_33 = np.percentile(scores['monetary'], 33)
monetary_67 = np.percentile(scores['monetary'], 67)
conditions_monetary = [
scores['monetary'] <= monetary_33,
(scores['monetary'] > monetary_33) & (scores['monetary'] <= monetary_67),
scores['monetary'] > monetary_67
]
choices_monetary = [1, 2, 3]
scores['M'] = np.select(conditions_monetary, choices_monetary, default=1)
else:
scores['M'] = 1
return scores
def assign_segments(self, scores):
"""Назначение RFM-сегментов"""
segments = []
for _, row in scores.iterrows():
r, f, m = row['R'], row['F'], row['M']
if r == 1 and f == 3 and m == 3:
segment = "Champions"
elif r == 1 and f >= 2 and m >= 2:
segment = "Loyal Customers"
elif r == 1:
segment = "New Customers"
elif r == 2 and f >= 2 and m >= 2:
segment = "Potential Loyalists"
elif r == 3 and f >= 2 and m >= 2:
segment = "At Risk"
elif r == 3 and f == 1 and m == 1:
segment = "Lost Customers"
else:
segment = "Need Attention"
segments.append(segment)
scores['segment'] = segments
scores['RFM'] = scores['R'].astype(str) + '-' + scores['F'].astype(str) + '-' + scores['M'].astype(str)
return scores
def analyze_direction(self, direction, show_plots=True, show_report=True):
"""Полный анализ для одного направления"""
if show_report:
print(f"\n{'='*60}")
print(f"АНАЛИЗ ДЛЯ НАПРАВЛЕНИЯ: {direction}")
print(f"{'='*60}")
try:
# Расчёт метрик
rfm_metrics = self.calculate_rfm_metrics(direction)
if show_report:
print(f"Рассчитано метрик для {len(rfm_metrics)} клиентов")
scores = self.calculate_scores(rfm_metrics)
if show_report:
print(f"Рассчитано баллов для {len(scores)} клиентов")
scores = self.assign_segments(scores)
if show_report:
print(f"Назначены сегменты для {len(scores)} клиентов")
# Всегда рассчитываем segment_data, даже если не показываем графики
segment_data = scores.groupby('segment').agg({
'monetary': ['sum', 'count'],
'recency': 'mean',
'frequency': 'mean'
}).round(2)
segment_data.columns = ['revenue', 'clients_count', 'avg_recency', 'avg_frequency']
segment_data = segment_data.reset_index()
# Визуализации (только если нужно показывать)
if show_plots:
self._create_visualizations(scores, direction, segment_data)
# Отчет (только если нужно показывать)
if show_report:
report = self._generate_report(scores, direction, segment_data)
else:
report = None
# Добавляем направление в данные
scores['direction'] = direction
# Сохраняем результаты для последующего сравнения
self.all_scores[direction] = scores
self.all_segment_data[direction] = segment_data
return scores, segment_data, report
except Exception as e:
print(f"Ошибка при анализе направления {direction}: {e}")
import traceback
traceback.print_exc()
return None, None, None
def _create_visualizations(self, scores, direction, segment_data=None):
"""Создание визуализаций для направления (внутренний метод)"""
# Если segment_data не передан, рассчитываем его
if segment_data is None:
segment_data = scores.groupby('segment').agg({
'monetary': ['sum', 'count'],
'recency': 'mean',
'frequency': 'mean'
}).round(2)
segment_data.columns = ['revenue', 'clients_count', 'avg_recency', 'avg_frequency']
segment_data = segment_data.reset_index()
# 1. ГРАФИК: Круговая диаграмма: Распределение выручки по RFM-сегментам
if len(segment_data) > 0:
fig1 = px.pie(segment_data,
values='revenue',
names='segment',
title=f'{direction}: Распределение выручки по RFM-сегментам',
hole=0.4)
fig1.update_traces(textposition='inside', textinfo='percent+label')
fig1.show()
# 2. ГРАФИК: Сравнение сегментов: оборот и количество клиентов
if len(segment_data) > 0:
fig2 = make_subplots(specs=[[{"secondary_y": True}]])
fig2.add_trace(
go.Bar(x=segment_data['segment'], y=segment_data['revenue']/1e6,
name="Оборот (млн ₽)", marker_color='blue'),
secondary_y=False,
)
fig2.add_trace(
go.Scatter(x=segment_data['segment'], y=segment_data['clients_count'],
name="Количество клиентов", marker_color='red', mode='lines+markers'),
secondary_y=True,
)
fig2.update_layout(
title_text=f"{direction}: Сравнение сегментов - оборот и количество клиентов",
xaxis_title="RFM-сегменты"
)
fig2.update_yaxes(title_text="Оборот (млн ₽)", secondary_y=False)
fig2.update_yaxes(title_text="Количество клиентов", secondary_y=True)
fig2.show()
# 3. ГРАФИК: Средние метрики по направлению между сегментами
if len(segment_data) > 0:
fig3 = go.Figure(data=[
go.Bar(name='Средний Recency', x=segment_data['segment'], y=segment_data['avg_recency']),
go.Bar(name='Средняя Frequency', x=segment_data['segment'], y=segment_data['avg_frequency'])
])
fig3.update_layout(
title=f"{direction}: Средние метрики по сегментам",
xaxis_title="RFM-сегменты",
yaxis_title="Значения метрик",
barmode='group'
)
fig3.show()
# 4. ГРАФИК: Распределение клиентов по сегментам
if len(segment_data) > 0:
fig4 = px.bar(segment_data,
x='segment',
y='clients_count',
title=f'{direction}: Распределение уникальных клиентов по RFM-сегментам',
labels={'segment': 'RFM-сегмент', 'clients_count': 'Количество клиентов'},
text='clients_count',
color='segment')
fig4.update_traces(texttemplate='%{text}', textposition='outside')
fig4.update_layout(showlegend=False, xaxis_tickangle=45)
fig4.show()
# 5. ГРАФИК: Доля клиентов по сегментам
if len(segment_data) > 0:
total_clients = segment_data['clients_count'].sum()
segment_data['percentage'] = (segment_data['clients_count'] / total_clients * 100).round(1)
fig5 = px.pie(segment_data,
values='clients_count',
names='segment',
title=f'{direction}: Доля клиентов по RFM-сегментам',
hole=0.4)
fig5.update_traces(textposition='inside', textinfo='percent+label+value')
fig5.show()
return segment_data
def _generate_report(self, scores, direction, segment_data):
"""Генерация отчета по направлению (внутренний метод)"""
total_revenue = scores['monetary'].sum() / 1e6
total_clients = len(scores)
champions_data = segment_data[segment_data['segment'] == 'Champions']
if not champions_data.empty:
champions_revenue = champions_data['revenue'].iloc[0] / 1e6
champions_clients = champions_data['clients_count'].iloc[0]
champions_percent = (champions_revenue / total_revenue) * 100 if total_revenue > 0 else 0
else:
champions_revenue = 0
champions_clients = 0
champions_percent = 0
lost_data = segment_data[segment_data['segment'] == 'Lost Customers']
if not lost_data.empty:
lost_clients = lost_data['clients_count'].iloc[0]
lost_percent = (lost_clients / total_clients) * 100 if total_clients > 0 else 0
else:
lost_clients = 0
lost_percent = 0
report = f"""
ОТЧЕТ ДЛЯ НАПРАВЛЕНИЯ: {direction}
{'='*50}
ОБЩАЯ СТАТИСТИКА:
Общая выручка: {total_revenue:.1f} млн ₽
Всего клиентов: {total_clients}
Выручка от Champions: {champions_revenue:.1f} млн ₽ ({champions_percent:.1f}%)
РАСПРЕДЕЛЕНИЕ СЕГМЕНТОВ:
"""
for segment in segment_data.itertuples():
revenue_percent = (segment.revenue / 1e6 / total_revenue) * 100 if total_revenue > 0 else 0
clients_percent = (segment.clients_count / total_clients) * 100 if total_clients > 0 else 0
report += f"{segment.segment}: {segment.clients_count} клиентов ({clients_percent:.1f}%), {segment.revenue/1e6:.1f} млн ₽ ({revenue_percent:.1f}%)\n"
report += f"""
РЕКОМЕНДАЦИИ ДЛЯ {direction}:
"""
if champions_percent > 50:
report += "Отличная концентрация! Champions приносят более 50% выручки\n"
report += "Усилить программы лояльности для Champions\n"
else:
report += "Необходимо увеличить долю Champions в выручке\n"
report += f"Потеряно {lost_percent:.1f}% клиентов\n"
report += "Проанализировать причины оттока\n"
print(report)
return report
def analyze_multiple_directions(self, directions, show_plots=True, show_reports=True):
"""Анализ нескольких направлений без вывода сравнения"""
print("🚀 Запуск анализа направлений...")
for direction in directions:
self.analyze_direction(direction, show_plots=show_plots, show_report=show_reports)
print(f"✅ Анализ завершен для {len(self.all_scores)} направлений")
return self.all_scores, self.all_segment_data
def compare_directions(self):
"""Сравнительная аналитика по всем проанализированным направлениям"""
if len(self.all_scores) < 2:
print(f"⚠️ Для сравнения нужно минимум 2 направления. Сейчас проанализировано: {len(self.all_scores)}")
return None
print(f"\n{'='*80}")
print("📊 СРАВНИТЕЛЬНАЯ АНАЛИТИКА ПО НАПРАВЛЕНИЯМ")
print(f"{'='*80}")
# Объединяем все данные
combined_data = pd.concat([scores for scores in self.all_scores.values() if scores is not None], ignore_index=True)
# 1. Распределение клиентов по RFM-сегментам в разных направлениях
segment_dist = pd.crosstab(combined_data['direction'], combined_data['segment'])
fig1 = px.bar(segment_dist,
title="Распределение клиентов по RFM-сегментам в разных направлениях",
labels={'value': 'Количество клиентов', 'direction': 'Направление'},
barmode='group')
fig1.show()
# 2. Процентное распределение клиентов по RFM-сегментам
segment_percent = segment_dist.div(segment_dist.sum(axis=1), axis=0) * 100
fig2 = px.bar(segment_percent,
title="Процентное распределение клиентов по RFM-сегментам",
labels={'value': 'Процент клиентов', 'direction': 'Направление'},
barmode='group')
fig2.show()
# 3. Сводная таблица по направлениям
summary = combined_data.groupby('direction').agg({
'monetary': ['sum', 'count'],
'recency': 'mean',
'frequency': 'mean'
}).round(2)
summary.columns = ['Выручка', 'Клиенты', 'Ср. Recency', 'Ср. Frequency']
summary['Выручка на клиента'] = (summary['Выручка'] / summary['Клиенты']).round(2)
# Добавляем количество уникальных клиентов по сегментам
champions_count = combined_data[combined_data['segment'] == 'Champions'].groupby('direction').size()
loyal_count = combined_data[combined_data['segment'] == 'Loyal Customers'].groupby('direction').size()
new_count = combined_data[combined_data['segment'] == 'New Customers'].groupby('direction').size()
potential_count = combined_data[combined_data['segment'] == 'Potential Loyalists'].groupby('direction').size()
at_risk_count = combined_data[combined_data['segment'] == 'At Risk'].groupby('direction').size()
lost_count = combined_data[combined_data['segment'] == 'Lost Customers'].groupby('direction').size()
need_attention_count = combined_data[combined_data['segment'] == 'Need Attention'].groupby('direction').size()
summary['Champions'] = champions_count
summary['Loyal Customers'] = loyal_count
summary['New Customers'] = new_count
summary['Potential Loyalists'] = potential_count
summary['At Risk'] = at_risk_count
summary['Lost Customers'] = lost_count
summary['Need Attention'] = need_attention_count
# Заполняем пропуски нулями
summary = summary.fillna(0)
print("📈 СВОДНАЯ ТАБЛИЦА ПО НАПРАВЛЕНИЯМ:")
print(summary)
# 4. Сравнение выручки по направлениям
fig3 = px.bar(summary.reset_index(),
x='direction', y='Выручка',
title='Выручка по направлениям',
labels={'direction': 'Направление', 'Выручка': 'Выручка'})
fig3.show()
# 5. Сравнение количества клиентов
fig4 = px.bar(summary.reset_index(),
x='direction', y='Клиенты',
title='Общее количество клиентов по направлениям',
labels={'direction': 'Направление', 'Клиенты': 'Количество клиентов'})
fig4.show()
# 6. Количество уникальных клиентов по сегментам (новый график)
segment_counts = summary[['Champions', 'Loyal Customers', 'New Customers',
'Potential Loyalists', 'At Risk', 'Lost Customers', 'Need Attention']]
fig5 = px.bar(segment_counts.reset_index(),
x='direction',
y=segment_counts.columns,
title='Количество уникальных клиентов по сегментам и направлениям',
labels={'direction': 'Направление', 'value': 'Количество клиентов', 'variable': 'Сегмент'},
barmode='group')
fig5.show()
# 7. Процентное распределение клиентов по сегментам (столбчатая диаграмма 100%)
segment_percent_total = segment_counts.div(segment_counts.sum(axis=1), axis=0) * 100
fig6 = px.bar(segment_percent_total.reset_index(),
x='direction',
y=segment_percent_total.columns,
title='Процентное распределение клиентов по сегментам (100% stacked)',
labels={'direction': 'Направление', 'value': 'Процент клиентов', 'variable': 'Сегмент'},
barmode='relative')
fig6.update_yaxes(title_text='Процент клиентов (%)')
fig6.show()
# 8. Сравнение средних метрик по направлениям
metrics_comparison = summary[['Ср. Recency', 'Ср. Frequency', 'Выручка на клиента']].reset_index()
fig7 = make_subplots(rows=1, cols=3,
subplot_titles=('Средний Recency', 'Средняя Frequency', 'Выручка на клиента'))
fig7.add_trace(go.Bar(x=metrics_comparison['direction'], y=metrics_comparison['Ср. Recency']),
row=1, col=1)
fig7.add_trace(go.Bar(x=metrics_comparison['direction'], y=metrics_comparison['Ср. Frequency']),
row=1, col=2)
fig7.add_trace(go.Bar(x=metrics_comparison['direction'], y=metrics_comparison['Выручка на клиента']),
row=1, col=3)
fig7.update_layout(title_text='Сравнение средних метрик по направлениям', showlegend=False)
fig7.show()
return summary
def analyze_clients_distribution(self, direction):
"""Детальный анализ распределения клиентов для одного направления"""
if direction not in self.all_scores:
print(f"⚠️ Направление {direction} не проанализировано. Сначала выполните analyze_direction()")
return None
scores = self.all_scores[direction]
segment_data = self.all_segment_data.get(direction)
if segment_data is None:
segment_data = scores.groupby('segment').agg({
'monetary': ['sum', 'count'],
'recency': 'mean',
'frequency': 'mean'
}).round(2)
segment_data.columns = ['revenue', 'clients_count', 'avg_recency', 'avg_frequency']
segment_data = segment_data.reset_index()
print(f"\n{'='*60}")
print(f"📊 АНАЛИЗ КЛИЕНТОВ ДЛЯ НАПРАВЛЕНИЯ: {direction}")
print(f"{'='*60}")
total_clients = len(scores)
print(f"Всего уникальных клиентов: {total_clients}")
# 1. Столбчатая диаграмма распределения клиентов
fig1 = px.bar(segment_data,
x='segment',
y='clients_count',
title=f'{direction}: Распределение клиентов по RFM-сегментам',
labels={'segment': 'RFM-сегмент', 'clients_count': 'Количество клиентов'},
text='clients_count',
color='segment')
fig1.update_traces(texttemplate='%{text}', textposition='outside')
fig1.update_layout(showlegend=False, xaxis_tickangle=45)
fig1.show()
# 2. Круговая диаграмма долей клиентов
segment_data['percentage'] = (segment_data['clients_count'] / total_clients * 100).round(1)
fig2 = px.pie(segment_data,
values='clients_count',
names='segment',
title=f'{direction}: Доля клиентов по RFM-сегментам',
hole=0.4)
fig2.update_traces(textposition='inside', textinfo='percent+label+value')
fig2.show()
# 3. Сравнение количества клиентов и выручки
fig3 = make_subplots(specs=[[{"secondary_y": True}]])
fig3.add_trace(
go.Bar(x=segment_data['segment'], y=segment_data['clients_count'],
name="Количество клиентов", marker_color='lightblue'),
secondary_y=False,
)
fig3.add_trace(
go.Scatter(x=segment_data['segment'], y=segment_data['revenue']/1e6,
name="Выручка (млн ₽)", marker_color='red', mode='lines+markers'),
secondary_y=True,
)
fig3.update_layout(
title_text=f"{direction}: Сравнение количества клиентов и выручки по сегментам",
xaxis_title="RFM-сегменты"
)
fig3.update_yaxes(title_text="Количество клиентов", secondary_y=False)
fig3.update_yaxes(title_text="Выручка (млн ₽)", secondary_y=True)
fig3.show()
# 4. Топ сегменты по количеству клиентов
top_segments = segment_data.nlargest(5, 'clients_count')[['segment', 'clients_count', 'percentage']]
fig4 = px.bar(top_segments,
x='segment',
y='clients_count',
title=f'{direction}: Топ-5 сегментов по количеству клиентов',
labels={'segment': 'RFM-сегмент', 'clients_count': 'Количество клиентов'},
text='percentage',
color='segment')
fig4.update_traces(texttemplate='%{text}%', textposition='outside')
fig4.update_layout(showlegend=False)
fig4.show()
# Вывод статистики
print("\n📈 СТАТИСТИКА ПО КЛИЕНТАМ:")
print("-" * 40)
for _, row in segment_data.iterrows():
print(f"{row['segment']}: {row['clients_count']} клиентов ({row['percentage']}%)")
champions = segment_data[segment_data['segment'] == 'Champions']
if not champions.empty:
print(f"\n🎯 Champions: {champions['clients_count'].iloc[0]} клиентов ({champions['percentage'].iloc[0]}%)")
lost = segment_data[segment_data['segment'] == 'Lost Customers']
if not lost.empty:
print(f"⚠️ Lost Customers: {lost['clients_count'].iloc[0]} клиентов ({lost['percentage'].iloc[0]}%)")
return segment_data
def save_to_excel(self, filename='rfm_analysis_results.xlsx'):
"""Сохранение всех результатов в Excel"""
if not self.all_scores:
print("⚠️ Нет данных для сохранения")
return
try:
with pd.ExcelWriter(filename) as writer:
# Все клиенты
all_clients_data = pd.concat(self.all_scores.values(), ignore_index=True)
all_clients_data.to_excel(writer, sheet_name='Все клиенты', index=False)
# По направлениям
for direction, scores in self.all_scores.items():
sheet_name = f'{direction}'[:31] # Ограничение длины имени листа
# Переупорядочиваем колонки, чтобы id_head был первой
columns_order = ['id_head', 'direction', 'recency', 'frequency', 'monetary',
'R', 'F', 'M', 'RFM', 'segment']
# Добавляем отсутствующие колонки
for col in columns_order:
if col not in scores.columns:
columns_order.remove(col)
scores_reordered = scores[columns_order]
scores_reordered.to_excel(writer, sheet_name=sheet_name, index=False)
# Сводная таблица по направлениям
if len(self.all_scores) >= 1:
combined_data = pd.concat(self.all_scores.values(), ignore_index=True)
summary = combined_data.groupby('direction').agg({
'monetary': ['sum', 'count'],
'recency': 'mean',
'frequency': 'mean'
}).round(2)
summary.columns = ['Выручка', 'Клиенты', 'Ср. Recency', 'Ср. Frequency']
summary['Выручка на клиента'] = (summary['Выручка'] / summary['Клиенты']).round(2)
summary.to_excel(writer, sheet_name='Сводная')
# Сегменты по направлениям (только если есть данные)
valid_segment_data = {}
for direction, segment_data in self.all_segment_data.items():
if segment_data is not None and len(segment_data) > 0:
valid_segment_data[direction] = segment_data
if valid_segment_data:
try:
segment_summary = pd.concat(
valid_segment_data.values(),
keys=valid_segment_data.keys()
)
segment_summary.to_excel(writer, sheet_name='Сегменты')
except Exception as e:
print(f"⚠️ Не удалось сохранить сегменты: {e}")
# Сохраняем каждый сегмент отдельно
for direction, segment_data in valid_segment_data.items():
sheet_name = f'Сегменты_{direction}'[:31]
segment_data.to_excel(writer, sheet_name=sheet_name, index=False)
print(f"✅ Все данные сохранены в файл: {filename}")
print(f"📊 Сохранено клиентов: {len(all_clients_data)}")
print(f"🎯 Направления: {list(self.all_scores.keys())}")
except Exception as e:
print(f"❌ Ошибка при сохранении файла: {e}")
def save_to_excel_simple(self, filename='rfm_analysis_results.xlsx'):
"""Упрощенное сохранение результатов в Excel"""
if not self.all_scores:
print("⚠️ Нет данных для сохранения")
return
try:
with pd.ExcelWriter(filename) as writer:
# Все клиенты
all_clients_data = pd.concat(self.all_scores.values(), ignore_index=True)
all_clients_data.to_excel(writer, sheet_name='Все клиенты', index=False)
# По направлениям
for direction, scores in self.all_scores.items():
sheet_name = f'{direction}'[:31]
scores.to_excel(writer, sheet_name=sheet_name, index=False)
print(f"✅ Основные данные сохранены в файл: {filename}")
print(f"📊 Сохранено клиентов: {len(all_clients_data)}")
except Exception as e:
print(f"❌ Ошибка при сохранении файла: {e}")
# Инициализация анализатора
rfm_analyzer = RFMAnalysis(df)
print("✅ RFM анализатор инициализирован")
# Анализ одного направления 'some1' с полным выводом
direction = 'some1'
scores, segment_data, report = rfm_analyzer.analyze_direction(direction, show_plots=True, show_report=True)
# Дополнительный анализ клиентов одного направления 'some1'
rfm_analyzer.analyze_clients_distribution(direction)
# Анализ нескольких направлений с выводом графиков по направлениям
# directions = ['some1', 'some2']
# for direction in directions:
# scores, segment_data, report = rfm_analyzer.analyze_direction(direction, show_plots=True, show_report=True)
# Быстрый анализ всех направлений без вывода графиков по направлениям + сравнение
directions = ['some1', 'some2']
# Анализ без визуализаций (быстро)
all_scores, all_segments = rfm_analyzer.analyze_multiple_directions(
directions,
show_plots=False,
show_reports=False
)
# Сравнительная аналитика
summary = rfm_analyzer.compare_directions()
# Сохранение результатов в Excel
rfm_analyzer.save_to_excel('rfm_analysis_results.xlsx')
# Просмотр результатов
print("📊 ПРОСМОТР РЕЗУЛЬТАТОВ:")
print(f"Проанализировано направлений: {len(rfm_analyzer.all_scores)}")
print(f"Направления: {list(rfm_analyzer.all_scores.keys())}")
if rfm_analyzer.all_scores:
direction = list(rfm_analyzer.all_scores.keys())[0]
print(f"\nПример данных для направления {direction}:")
display(rfm_analyzer.all_scores[direction].head())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment