/
vikos7
/
Python_Lyubelskaya
Обзор
Документация
Войти
/
vikos7
/
Python_Lyubelskaya
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
app.py
266 строк
12 KB
Виктория
fix: исправлен фильтр по датам, убрано дублирование
28 июл 2026, 17:31
28 июл 2026, 17:31
c334718
Код
Авторство
О чём код?
import os import streamlit as st import pandas as pd import plotly.express as px import matplotlib.pyplot as plt import logging # Настройка логгера logging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[logging.StreamHandler()] # вывод в консоль ) logger = logging.getLogger(__name__) st.set_page_config(page_title="Анализ заказов", layout="wide") st.title("📊 Дашборд: заказы, пользователи, товары") @st.cache_data def load_and_prepare_data(): base_dir = os.path.dirname(os.path.abspath(__file__)) items_path = os.path.join(base_dir, "data", "items.csv") orders_path = os.path.join(base_dir, "data", "orders.csv") users_path = os.path.join(base_dir, "data", "users.csv") df_items = pd.read_csv(items_path) df_orders = pd.read_csv(orders_path) df_users = pd.read_csv(users_path) stats = { "total_orders": len(df_orders), "lost_to_users": 0, "lost_to_items": 0, "lost_na": 0, "lost_neg": 0 } # JOIN таблиц df = pd.merge(df_orders, df_users, on="user_id", how="inner") stats["lost_to_users"] = stats["total_orders"] - len(df) before_items = len(df) df = pd.merge(df, df_items, on="item_id", how="inner") stats["lost_to_items"] = before_items - len(df) # Приведение типов date_cols = [c for c in df.columns if "date" in c.lower()] for c in date_cols: df[c] = pd.to_datetime(df[c], errors="coerce") logger.debug(f"[LOAD-DATA] Колонка '{c}' преобразована. Тип: {df[c].dtype}, NaT count: {df[c].isna().sum()}") num_cols = ["quantity", "price_per_unit", "base_price"] for c in num_cols: if c in df.columns: df[c] = pd.to_numeric(df[c], errors="coerce") # Очистка пропусков и отрицательных цен cat_cols = df.select_dtypes(include=["object", "string"]).columns for col in cat_cols: df[col] = df[col].fillna("Unknown") critical_cols = ["price_per_unit", "quantity"] initial_len = len(df) df = df.dropna(subset=critical_cols) stats["lost_na"] = initial_len - len(df) before_neg = len(df) if "price_per_unit" in df.columns: df = df[df["price_per_unit"] >= 0] stats["lost_neg"] = before_neg - len(df) # Расчёт выручки и доп. колонок df["revenue"] = df["price_per_unit"] * df["quantity"] if "order_date" in df.columns and pd.api.types.is_datetime64_any_dtype(df["order_date"]): df["order_month"] = df["order_date"].dt.to_period("M") df["day_of_week"] = df["order_date"].dt.day_name() print("Тип order_date:", df["order_date"].dtype) print("Есть ли NaT:", df["order_date"].isna().sum()) print("Первые 5 значений:", df["order_date"].head()) return df, stats df, stats = load_and_prepare_data() # --------------------------------------------------------- # БЛОК 5.1: Сырые данные + фильтры # --------------------------------------------------------- tab1, tab2, tab3, tab4 = st.tabs(["Сырые данные", "Ключевые показатели", "Визуализация", "Аналитические выводы"]) with tab1: st.header("📋 Сырые данные (итоговый датасет)") # Фильтры — теперь 4 колонки через st.columns, но можно и 3, если места мало col1, col2, col3, col4 = st.columns(4) min_date = max_date = None if "order_date" in df.columns: min_date = df["order_date"].min() max_date = df["order_date"].max() date_range = col1.date_input("Диапазон дат", value=(min_date, max_date) if min_date and max_date else None) else: date_range = None # чтобы дальше не было ошибки categories = ["Все"] if "category" in df.columns: categories = ["Все"] + sorted(df["category"].unique().astype(str)) selected_category = col2.selectbox("Категория товара", categories) else: selected_category = "Все" cities = ["Все"] if "city" in df.columns: cities = ["Все"] + sorted(df["city"].unique().astype(str)) selected_city = col3.selectbox("Город", cities) else: selected_city = "Все" segments = ["Все"] if "user_segment" in df.columns: segments = ["Все"] + sorted(df["user_segment"].unique().astype(str)) selected_segment = col4.selectbox("Сегмент клиента", segments) else: selected_segment = "Все" # Применяем фильтры filtered_df = df.copy() logger.info("[FILTER-START] Подготовка фильтрации по дате") logger.debug(f"[FILTER-START] Тип order_date: {filtered_df['order_date'].dtype}") logger.debug(f"[FILTER-START] NaT count: {filtered_df['order_date'].isna().sum()}") if not pd.api.types.is_datetime64_any_dtype(filtered_df["order_date"]): logger.error("[FILTER-FAIL] order_date не является datetime — фильтр по дате пропускается.") st.error("Фильтр по дате не может быть применён: тип order_date некорректен.") else: logger.info("[FILTER-OK] Тип datetime подтверждён, применяем фильтр.") if date_range and isinstance(date_range, tuple) and len(date_range) == 2: start_date_raw, end_date_raw = date_range # это datetime.date из date_input # Превращаем в Timestamp (добавляем время 00:00:00) start_date = pd.Timestamp(start_date_raw) end_date = pd.Timestamp(end_date_raw) + pd.Timedelta(days=1) - pd.Timedelta(nanoseconds=1) # ↑ end_date делаем «чуть меньше чем полночь следующих суток», чтобы захватить весь день включительно filtered_df = filtered_df[ (filtered_df["order_date"] >= start_date) & (filtered_df["order_date"] <= end_date) ] if selected_category != "Все": filtered_df = filtered_df[filtered_df["category"] == selected_category] if selected_city != "Все": filtered_df = filtered_df[filtered_df["city"] == selected_city] if selected_segment != "Все": filtered_df = filtered_df[filtered_df["user_segment"] == selected_segment] st.write(f"Отображается строк: {len(filtered_df)} из {len(df)}") st.dataframe(filtered_df, use_container_width=True) # --------------------------------------------------------- # БЛОК 5.2: Ключевые показатели # --------------------------------------------------------- with tab2: st.header("💰 Ключевые показатели") total_revenue = filtered_df["revenue"].sum() unique_users = filtered_df["user_id"].nunique() total_orders = len(filtered_df) avg_check = total_revenue / total_orders if total_orders > 0 else 0 col1, col2, col3, col4 = st.columns(4) col1.metric("Общее количество заказов", total_orders) col2.metric("Общая выручка", f"{total_revenue:,.0f} ₽") col3.metric("Уникальных пользователей", unique_users) col4.metric("Средний чек", f"{avg_check:,.2f} ₽") # --------------------------------------------------------- # БЛОК 5.3: Визуализация (matplotlib) # --------------------------------------------------------- with tab3: st.header("📈 Визуализация") # Топ-10 товаров по выручке (горизонтальная столбчатая) st.subheader("🏆 Топ‑10 товаров по выручке") top_10 = ( filtered_df.groupby("item_name", dropna=False)["revenue"] .sum() .sort_values(ascending=False) .head(10) ) fig, ax = plt.subplots(figsize=(10, 6)) ax.barh(top_10.index, top_10.values) ax.set_xlabel("Выручка") ax.invert_yaxis() st.pyplot(fig) plt.close(fig) # Выручка по категориям (круговая диаграмма) st.subheader("🏷️ Доля категорий в выручке") if "category" in filtered_df.columns: cat_rev = filtered_df.groupby("category", dropna=False)["revenue"].sum() fig, ax = plt.subplots(figsize=(8, 8)) ax.pie(cat_rev.values, labels=cat_rev.index, autopct='%1.1f%%') ax.set_title("Доля категорий в общей выручке") st.pyplot(fig) plt.close(fig) else: st.warning("Колонка 'category' отсутствует — нельзя построить диаграмму по категориям.") # Зависимость количества заказов от дня недели st.subheader("🗓️ Количество заказов по дням недели") if "day_of_week" in filtered_df.columns: day_counts = filtered_df["day_of_week"].value_counts().reindex([ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ], fill_value=0) fig, ax = plt.subplots(figsize=(10, 5)) ax.bar(day_counts.index, day_counts.values) ax.set_ylabel("Количество заказов") ax.tick_params(axis='x', rotation=45) st.pyplot(fig) plt.close(fig) else: st.warning("Дата не загружена корректно — нельзя построить график по дням недели.") # --------------------------------------------------------- # БЛОК 5.4: Аналитические выводы # --------------------------------------------------------- with tab4: st.header("🧠 Аналитические выводы") # Формируем выводы на основе данных conclusions = [] if "category" in filtered_df.columns: top_cat = filtered_df.groupby("category")["revenue"].sum().idxmax() conclusions.append(f"• Основная выручка приходится на категорию **«{top_cat}»**.") if len(top_10) > 0: top_item = top_10.index[0] conclusions.append(f"• Лидером продаж является товар **«{top_item}»**.") if "day_of_week" in filtered_df.columns: peak_day = filtered_df["day_of_week"].value_counts().idxmax() conclusions.append(f"• Пик заказов наблюдается в **{peak_day}**.") conclusions.append(f"• Всего обработано заказов: **{total_orders}**, выручка составила **{total_revenue:,.0f} ₽**.") st.markdown("### Основные выводы:") for line in conclusions: st.write(line) with st.expander("📄 Отчёт: потери строк на каждом этапе (исходная загрузка)"): col1, col2, col3 = st.columns(3) col1.metric("Исходно заказов", stats["total_orders"]) col2.metric("Нет пользователя", stats["lost_to_users"]) col3.metric("Нет товара", stats["lost_to_items"]) col4, col5, col6 = st.columns(3) col4.metric("Пропуски в цене/кол-ве", stats["lost_na"]) col5.metric("Отрицательные цены", stats["lost_neg"]) col6.metric("Итого строк в дашборде", len(filtered_df))