Spaces:
Sleeping
Sleeping
| # modules/visualization.py | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| from matplotlib import font_manager | |
| import numpy as np | |
| import streamlit as st | |
| import config | |
| from modules.profile_utils import get_chat_profile_dict | |
| logger = config.get_logger(__name__) | |
| def set_korean_font(): | |
| """ | |
| ์์คํ ์ ์ค์น๋ ํ๊ธ ํฐํธ๋ฅผ ์ฐพ์ Matplotlib์ ์ค์ ํฉ๋๋ค. | |
| """ | |
| font_list = ['Malgun Gothic', 'AppleGothic', 'NanumGothic'] | |
| found_font = False | |
| for font_name in font_list: | |
| if any(font.name == font_name for font in font_manager.fontManager.ttflist): | |
| plt.rc('font', family=font_name) | |
| logger.info(f"โ ํ๊ธ ํฐํธ '{font_name}'์(๋ฅผ) ์ฐพ์ ๊ทธ๋ํ์ ์ ์ฉํฉ๋๋ค.") | |
| found_font = True | |
| break | |
| if not found_font: | |
| logger.warning("โ ๏ธ ๊ฒฝ๊ณ : Malgun Gothic, AppleGothic, NanumGothic ํฐํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.") | |
| plt.rcParams['axes.unicode_minus'] = False | |
| def display_merchant_profile(profile_data: dict): | |
| set_korean_font() | |
| """ | |
| ๋ถ์๋ ๊ฐ๋งน์ ํ๋กํ ์ ์ฒด๋ฅผ Streamlit ํ๋ฉด์ ์๊ฐํํฉ๋๋ค. | |
| """ | |
| if not profile_data or "store_profile" not in profile_data: | |
| st.error("๋ถ์ํ ๊ฐ๋งน์ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.") | |
| return | |
| store_data = profile_data["store_profile"] | |
| store_name = store_data.get('๊ฐ๋งน์ ๋ช ', '์ ํ ๋งค์ฅ') | |
| st.info(f"**'{store_name}'**์ ์์ธ ๋ถ์ ๊ฒฐ๊ณผ์ ๋๋ค.") | |
| tab1, tab2, tab3, tab4 = st.tabs([ | |
| "๐ ๊ธฐ๋ณธ ์ ๋ณด", | |
| "๐งโ๐คโ๐ง ์ฃผ์ ๊ณ ๊ฐ์ธต (์ฑ๋ณ/์ฐ๋ น๋)", | |
| "๐ถ ์ฃผ์ ๊ณ ๊ฐ ์ ํ (์๊ถ)", | |
| "๐ ๊ณ ๊ฐ ์ถฉ์ฑ๋ (์ ๊ท/์ฌ๋ฐฉ๋ฌธ)" | |
| ]) | |
| with tab1: | |
| render_basic_info_table(store_data) | |
| with tab2: | |
| st.subheader("๐งโ๐คโ๐ง ์ฃผ์ ๊ณ ๊ฐ์ธต ๋ถํฌ (์ฑ๋ณ/์ฐ๋ น๋)") | |
| fig2 = plot_customer_distribution(store_data) | |
| st.pyplot(fig2) | |
| with tab3: | |
| st.subheader("๐ถ ์ฃผ์ ๊ณ ๊ฐ ์ ํ (์๊ถ)") | |
| fig3 = plot_customer_type_pie(store_data) | |
| st.pyplot(fig3) | |
| with tab4: | |
| st.subheader("๐ ์ ๊ท vs ์ฌ๋ฐฉ๋ฌธ ๊ณ ๊ฐ ๋น์จ") | |
| fig4 = plot_loyalty_donut(store_data) | |
| st.pyplot(fig4) | |
| def get_main_customer_segment(store_data): | |
| """์ฃผ์ ๊ณ ๊ฐ์ธต(์ฑ๋ณ/์ฐ๋ น๋) ํ ์คํธ๋ฅผ ๋ฐํํฉ๋๋ค.""" | |
| segments = { | |
| '๋จ์ฑ 20๋ ์ดํ': store_data.get('๋จ์ฑ20๋์ดํ๋น์จ', 0), | |
| '๋จ์ฑ 30๋': store_data.get('๋จ์ฑ30๋๋น์จ', 0), | |
| '๋จ์ฑ 40๋': store_data.get('๋จ์ฑ40๋๋น์จ', 0), | |
| '๋จ์ฑ 50๋ ์ด์': store_data.get('๋จ์ฑ50๋๋น์จ', 0) + store_data.get('๋จ์ฑ60๋์ด์๋น์จ', 0), | |
| '์ฌ์ฑ 20๋ ์ดํ': store_data.get('์ฌ์ฑ20๋์ดํ๋น์จ', 0), | |
| '์ฌ์ฑ 30๋': store_data.get('์ฌ์ฑ30๋๋น์จ', 0), | |
| '์ฌ์ฑ 40๋': store_data.get('์ฌ์ฑ40๋๋น์จ', 0), | |
| '์ฌ์ฑ 50๋ ์ด์': store_data.get('์ฌ์ฑ50๋๋น์จ', 0) + store_data.get('์ฌ์ฑ60๋์ด์๋น์จ', 0) | |
| } | |
| if not any(segments.values()): | |
| return None | |
| max_segment = max(segments, key=segments.get) | |
| max_value = segments[max_segment] | |
| if max_value == 0: | |
| return None | |
| return f"'{max_segment}({max_value:.1f}%)'" | |
| def render_basic_info_table(store_data): | |
| """(Tab 1) ๊ธฐ๋ณธ ์ ๋ณด ์์ฝ ํ์ ํ ์คํธ๋ฅผ ๋ ๋๋งํฉ๋๋ค.""" | |
| summary_data = get_chat_profile_dict(store_data) | |
| st.subheader("๐ ๊ฐ๋งน์ ๊ธฐ๋ณธ ์ ๋ณด") | |
| summary_df = pd.DataFrame(summary_data.items(), columns=["ํญ๋ชฉ", "๋ด์ฉ"]) | |
| summary_df = summary_df[summary_df['ํญ๋ชฉ'] != '์๋์ถ์ถํน์ง'] | |
| summary_df = summary_df.astype(str) | |
| st.table(summary_df.set_index('ํญ๋ชฉ')) | |
| st.subheader("๐ ๋ถ์ ์์ฝ") | |
| st.write(f"โ **{summary_data.get('๊ฐ๋งน์ ๋ช ', 'N/A')}**์(๋) '{summary_data.get('์๊ถ', 'N/A')}' ์๊ถ์ '{summary_data.get('์ ์ข ', 'N/A')}' ์ ์ข ๊ฐ๋งน์ ์ ๋๋ค.") | |
| st.write(f"๐ ๋งค์ถ ์์ค์ **{summary_data.get('๋งค์ถ ์์ค', 'N/A')}**์ด๋ฉฐ, ๋์ผ ์๊ถ ๋ด ๋งค์ถ ์์๋ **{summary_data.get('๋์ผ ์๊ถ ๋๋น ๋งค์ถ ์์', 'N/A')}**์ ๋๋ค.") | |
| st.write(f"๐ฐ ๋ฐฉ๋ฌธ ๊ณ ๊ฐ์๋ **{summary_data.get('๋ฐฉ๋ฌธ ๊ณ ๊ฐ์ ์์ค', 'N/A')}** ์์ค์ด๋ฉฐ, ๊ฐ๋จ๊ฐ๋ **{summary_data.get('๊ฐ๋จ๊ฐ ์์ค', 'N/A')}** ์์ค์ ๋๋ค.") | |
| main_customer = get_main_customer_segment(store_data) | |
| if main_customer: | |
| st.write(f"๐ฅ ์ฃผ์ ๊ณ ๊ฐ์ธต์ **{main_customer}**์ด(๊ฐ) ๊ฐ์ฅ ๋ง์ต๋๋ค.") | |
| def plot_customer_distribution(store_data): | |
| """(Tab 2) ๊ณ ๊ฐ ํน์ฑ ๋ถํฌ (์ฑ๋ณ/์ฐ๋ น๋)๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ง๋ ๊ทธ๋ํ๋ฅผ ์์ฑํฉ๋๋ค.""" | |
| labels = ['20๋ ์ดํ', '30๋', '40๋', '50๋ ์ด์'] | |
| male_percents = [ | |
| store_data.get('๋จ์ฑ20๋์ดํ๋น์จ', 0), store_data.get('๋จ์ฑ30๋๋น์จ', 0), | |
| store_data.get('๋จ์ฑ40๋๋น์จ', 0), | |
| store_data.get('๋จ์ฑ50๋๋น์จ', 0) + store_data.get('๋จ์ฑ60๋์ด์๋น์จ', 0) | |
| ] | |
| female_percents = [ | |
| store_data.get('์ฌ์ฑ20๋์ดํ๋น์จ', 0), store_data.get('์ฌ์ฑ30๋๋น์จ', 0), | |
| store_data.get('์ฌ์ฑ40๋๋น์จ', 0), | |
| store_data.get('์ฌ์ฑ50๋๋น์จ', 0) + store_data.get('์ฌ์ฑ60๋์ด์๋น์จ', 0) | |
| ] | |
| x = np.arange(len(labels)) | |
| width = 0.35 | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| rects1 = ax.bar(x - width/2, male_percents, width, label='๋จ์ฑ', color='cornflowerblue') | |
| rects2 = ax.bar(x + width/2, female_percents, width, label='์ฌ์ฑ', color='salmon') | |
| ax.set_ylabel('๊ณ ๊ฐ ๋น์จ (%)') | |
| ax.set_title('์ฃผ์ ๊ณ ๊ฐ์ธต ๋ถํฌ (์ฑ๋ณ/์ฐ๋ น๋)', fontsize=16) | |
| ax.set_xticks(x) | |
| ax.set_xticklabels(labels, fontsize=12) | |
| ax.legend() | |
| ax.grid(axis='y', linestyle='--', alpha=0.7) | |
| ax.bar_label(rects1, padding=3, fmt='%.1f') | |
| ax.bar_label(rects2, padding=3, fmt='%.1f') | |
| fig.tight_layout() | |
| return fig | |
| def plot_customer_type_pie(store_data): | |
| """(Tab 3) ์ฃผ์ ๊ณ ๊ฐ ์ ํ (๊ฑฐ์ฃผ์, ์ง์ฅ์ธ, ์ ๋์ธ๊ตฌ)์ ํ์ด ์ฐจํธ๋ก ์์ฑํฉ๋๋ค.""" | |
| customer_data = { | |
| '์ ๋์ธ๊ตฌ': store_data.get("์ ๋์ธ๊ตฌ์ด์ฉ๋น์จ", 0), | |
| '๊ฑฐ์ฃผ์': store_data.get("๊ฑฐ์ฃผ์์ด์ฉ๋น์จ", 0), | |
| '์ง์ฅ์ธ': store_data.get("์ง์ฅ์ธ์ด์ฉ๋น์จ", 0) | |
| } | |
| filtered_data = {label: (size or 0) for label, size in customer_data.items()} | |
| filtered_data = {label: size for label, size in filtered_data.items() if size > 0} | |
| sizes = list(filtered_data.values()) | |
| labels = list(filtered_data.keys()) | |
| if not sizes or sum(sizes) == 0: | |
| fig, ax = plt.subplots(figsize=(6, 6)) | |
| ax.text(0.5, 0.5, "๋ฐ์ดํฐ ์์", horizontalalignment='center', verticalalignment='center', transform=ax.transAxes) | |
| ax.set_title("์ฃผ์ ๊ณ ๊ฐ ์ ํ", fontsize=13) | |
| return fig | |
| pie_labels = [f"{label} ({size:.1f}%)" for label, size in zip(labels, sizes)] | |
| fig, ax = plt.subplots(figsize=(6, 6)) | |
| wedges, texts, autotexts = ax.pie( | |
| sizes, | |
| labels=pie_labels, | |
| autopct='%1.1f%%', | |
| startangle=90, | |
| pctdistance=0.8 | |
| ) | |
| plt.setp(autotexts, size=9, weight="bold", color="white") | |
| ax.set_title("์ฃผ์ ๊ณ ๊ฐ ์ ํ", fontsize=13) | |
| ax.axis('equal') | |
| return fig | |
| def plot_loyalty_donut(store_data): | |
| """(Tab 4) ์ ๊ท vs ์ฌ๋ฐฉ๋ฌธ ๊ณ ๊ฐ ๋น์จ์ ๋๋ ์ฐจํธ๋ก ์์ฑํฉ๋๋ค.""" | |
| visit_ratio = { | |
| '์ ๊ท ๊ณ ๊ฐ': store_data.get('์ ๊ท๊ณ ๊ฐ๋น์จ') or 0, | |
| '์ฌ์ด์ฉ ๊ณ ๊ฐ': store_data.get('์ฌ์ด์ฉ๊ณ ๊ฐ๋น์จ') or 0 | |
| } | |
| sizes = list(visit_ratio.values()) | |
| labels = list(visit_ratio.keys()) | |
| if not sizes or sum(sizes) == 0: | |
| fig, ax = plt.subplots(figsize=(5, 5)) | |
| ax.text(0.5, 0.5, "๋ฐ์ดํฐ ์์", horizontalalignment='center', verticalalignment='center', transform=ax.transAxes) | |
| ax.set_title("์ ๊ท vs ์ฌ๋ฐฉ๋ฌธ ๊ณ ๊ฐ ๋น์จ") | |
| return fig | |
| fig, ax = plt.subplots(figsize=(5, 5)) | |
| wedges, texts, autotexts = ax.pie( | |
| sizes, | |
| labels=labels, | |
| autopct='%1.1f%%', | |
| startangle=90, | |
| pctdistance=0.85, | |
| colors=['lightcoral', 'skyblue'] | |
| ) | |
| centre_circle = plt.Circle((0, 0), 0.70, fc='white') | |
| ax.add_artist(centre_circle) | |
| plt.setp(autotexts, size=10, weight="bold") | |
| ax.set_title("์ ๊ท vs ์ฌ๋ฐฉ๋ฌธ ๊ณ ๊ฐ ๋น์จ", fontsize=14) | |
| ax.axis('equal') | |
| return fig |