Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from streamlit_folium import st_folium | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| import japanize_matplotlib | |
| import seaborn as sns | |
| import numpy as np | |
| st.set_page_config(page_title="データ分析ダッシュボード", layout="wide") | |
| tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([" データ分析ダッシュボード", "Folium", "Network graph", "Ribbon graph", "Sankey diagram", "Waterfall Chart"]) | |
| with tab1: | |
| st.title("📊 データ分析ダッシュボード") | |
| uploaded_file = st.file_uploader("CSVファイルをアップロードしてください", type=["csv"]) | |
| if uploaded_file is not None: | |
| df = pd.read_csv(uploaded_file) | |
| st.success("✅ ファイルを読み込みました!") | |
| st.header("📌 データのプレビュー") | |
| st.dataframe(df.head()) | |
| st.header("📊 基本統計情報") | |
| st.write(df.describe()) | |
| st.header("📈 カラムごとの分布表示") | |
| # 数値型カラムを取得 | |
| numeric_columns = df.select_dtypes(include=np.number).columns | |
| # 数値型カラムが存在するか確認 | |
| if len(numeric_columns) > 0: | |
| column = st.selectbox("分布を見たいカラムを選んでください", numeric_columns) | |
| if column: # 選択されたカラムが存在する場合 | |
| fig, ax = plt.subplots() | |
| sns.histplot(df[column], kde=True, ax=ax) | |
| ax.set_title(f"{column} のヒストグラム") | |
| st.pyplot(fig) | |
| fig, ax = plt.subplots() | |
| sns.histplot(df[column], kde=True, ax=ax) | |
| ax.set_title(f"{column} のヒストグラム") | |
| st.pyplot(fig) | |
| numeric_df = df.select_dtypes(include=["number"]) | |
| fig_corr, ax_corr = plt.subplots() | |
| sns.heatmap(numeric_df.corr(), annot=True, cmap="coolwarm", fmt=".2f", ax=ax_corr) | |
| st.pyplot(fig_corr) | |
| st.header("📊 ボックスプロット") | |
| box_col = st.selectbox("ボックスプロットの対象カラムを選択", df.select_dtypes(include=np.number).columns, key="box") | |
| fig_box, ax_box = plt.subplots() | |
| sns.boxplot(x=df[box_col], ax=ax_box) | |
| ax_box.set_title(f"{box_col} のボックスプロット") | |
| st.pyplot(fig_box) | |
| st.header("📈 折れ線グラフ") | |
| if df.select_dtypes(include=np.number).shape[1] >= 2: | |
| line_x = st.selectbox("X軸に使うカラムを選択", df.select_dtypes(include=np.number).columns, key="line_x") | |
| line_y = st.selectbox("Y軸に使うカラムを選択", df.select_dtypes(include=np.number).columns, key="line_y") | |
| fig_line, ax_line = plt.subplots() | |
| sns.lineplot(x=df[line_x], y=df[line_y], ax=ax_line) | |
| ax_line.set_title(f"{line_x} vs {line_y} 折れ線グラフ") | |
| st.pyplot(fig_line) | |
| st.header("📊 カテゴリカルデータの棒グラフ") | |
| cat_columns = df.select_dtypes(include='object').columns | |
| if len(cat_columns) > 0: | |
| cat_col = st.selectbox("カテゴリカルカラムを選択", cat_columns) | |
| fig_bar, ax_bar = plt.subplots() | |
| df[cat_col].value_counts().plot(kind='bar', ax=ax_bar) | |
| ax_bar.set_title(f"{cat_col} の頻度棒グラフ") | |
| st.pyplot(fig_bar) | |
| else: | |
| st.warning("数値型のカラムがありません。") | |
| else: | |
| st.info("左側のサイドバーからCSVファイルをアップロードしてください。") | |
| with tab2: | |
| st.header("📊 住所・座標の可視化") | |
| import folium | |
| from streamlit_folium import st_folium | |
| import pandas as pd | |
| import requests | |
| # uploaded_file2 = st.file_uploader("住所CSVファイルをアップロードしてください", type=["csv"]) | |
| # if uploaded_file2 is not None: | |
| df = pd.read_csv("./address.csv") | |
| name_list = df["name"] | |
| address_list = df["address"] | |
| def get_lat_long_from_address(address): | |
| """住所をAPIで検索し、緯度と経度を取得する""" | |
| endpoint = "https://msearch.gsi.go.jp/address-search/AddressSearch" | |
| response = requests.get(endpoint, params={"q": address}) | |
| if response.status_code == 200: | |
| data = response.json() | |
| if len(data) > 0: | |
| lat = data[0]["geometry"]["coordinates"][1] | |
| lon = data[0]["geometry"]["coordinates"][0] | |
| return lat, lon | |
| return None, None | |
| staff_lat_lon = [get_lat_long_from_address(addr) for addr in address_list] | |
| data = pd.DataFrame({ | |
| "クリニック名": name_list, | |
| "クリニック住所": address_list, | |
| "クリニック_緯度": [lat for lat, lon in staff_lat_lon], | |
| "クリニック_経度": [lon for lat, lon in staff_lat_lon] | |
| }) | |
| col1, col2 = st.columns([3, 2]) | |
| # 地図の生成 | |
| m = folium.Map(location=[37.5, 140.5], zoom_start=7) | |
| # マーカーの追加 (NaNチェック 추가) | |
| for i in range(len(data["クリニック名"])): | |
| lat = data["クリニック_緯度"][i] | |
| lon = data["クリニック_経度"][i] | |
| if pd.isna(lat) or pd.isna(lon): | |
| continue | |
| folium.Marker( | |
| location=(lat, lon), | |
| popup=data["クリニック名"][i], | |
| icon=folium.Icon(color="blue"), | |
| ).add_to(m) | |
| with col1: | |
| st.title("📍 クリニックの位置情報") | |
| map_data = st_folium(m, width=1200, height=800) | |
| with col2: | |
| st.markdown("<br>", unsafe_allow_html=True) | |
| st.markdown("<br>", unsafe_allow_html=True) | |
| st.markdown("<br>", unsafe_allow_html=True) | |
| st.markdown("<br>", unsafe_allow_html=True) | |
| st.subheader("📌 選択されたクリニック情報") | |
| if map_data and map_data["last_object_clicked"]: | |
| clicked_lat = map_data["last_object_clicked"]["lat"] | |
| clicked_lon = map_data["last_object_clicked"]["lng"] | |
| for i in range(len(data["クリニック名"])): | |
| if (round(data["クリニック_緯度"][i], 6) == round(clicked_lat, 6)) and (round(data["クリニック_経度"][i], 6) == round(clicked_lon, 6)): | |
| st.write(f"**🏥 クリニック名:** {data['クリニック名'][i]}") | |
| st.write(f"**📍 住所:** {data['クリニック住所'][i]}") | |
| break | |
| else: | |
| st.info("📌 マーカーをクリックしてください。") | |
| with tab3: | |
| st.header("📊 ネットワークグラフ") | |
| from pyvis.network import Network | |
| import streamlit.components.v1 as components | |
| import pandas as pd | |
| import numpy as np | |
| np.random.seed(42) | |
| nodes = [f"Node_{i}" for i in range(1, 21)] | |
| edges = [] | |
| for _ in range(50): | |
| src = np.random.choice(nodes) | |
| dst = np.random.choice(nodes) | |
| if src != dst: | |
| weight = np.random.randint(1, 10) | |
| edges.append((src, dst, weight)) | |
| df = pd.DataFrame(edges, columns=["Source", "Target", "Weight"]) | |
| net = Network(height="800px", width="100%", bgcolor="#1e1e1e", font_color="white") | |
| net.force_atlas_2based(gravity=-500, central_gravity=0.03, spring_length=120, spring_strength=0.1) | |
| for node in nodes: | |
| net.add_node(node, label=node, title=f"{node} - Custom Tooltip", color="#29b6f6", font={'size': 20}) | |
| for src, dst, weight in edges: | |
| net.add_edge(src, dst, value=weight, title=f"Weight: {weight}", width=weight) | |
| net.show_buttons(filter_=["physics", "nodes", "edges"]) | |
| net.toggle_physics(True) | |
| net.set_edge_smooth('dynamic') | |
| net.save_graph('network.html') | |
| HtmlFile = open("network.html", 'r', encoding='utf-8') | |
| source_code = HtmlFile.read() | |
| components.html(source_code, height=850, scrolling=True) | |
| with tab4: | |
| import plotly.graph_objects as go | |
| import pandas as pd | |
| import streamlit as st | |
| st.header("📊 リボングラフ") | |
| data = { | |
| "categories": ["Botox", "Filler", "Laser Treatment", "Skincare", "Hair Transplant", "Others"], | |
| "q1_2021": [1500, 2500, 1000, 2000, 1800, 1600], | |
| "q2_2021": [1800, 2300, 1400, 1900, 1600, 1700], | |
| "q3_2021": [2200, 2400, 1600, 1800, 2000, 1500], | |
| "q4_2021": [2600, 2100, 1800, 1700, 2200, 1400], | |
| "q1_2022": [2400, 2500, 1900, 2100, 2300, 1200], | |
| "q2_2022": [2700, 2600, 1700, 2300, 2100, 1000], | |
| "q3_2022": [2800, 2800, 2000, 2400, 2500, 900], | |
| "q4_2022": [2900, 2900, 2100, 2400, 2700, 1100], | |
| "q1_2023": [3100, 3000, 2200, 2500, 2900, 1200], | |
| "q2_2023": [3300, 3100, 2400, 2600, 3000, 1300], | |
| "q3_2023": [3400, 3200, 2500, 2700, 3200, 1400], | |
| "q4_2023": [3500, 3300, 2600, 2800, 3300, 1500], | |
| } | |
| df = pd.DataFrame(data) | |
| final = pd.DataFrame(df["categories"]) | |
| quarters = [ | |
| "q1_2021", "q2_2021", "q3_2021", "q4_2021", | |
| "q1_2022", "q2_2022", "q3_2022", "q4_2022", | |
| "q1_2023", "q2_2023", "q3_2023", "q4_2023" | |
| ] | |
| for i, col in enumerate(quarters): | |
| ch1 = df.loc[:, ["categories", col]] | |
| ch1.sort_values(col, inplace=True) | |
| ch1[f"y{i}_upper"] = ch1[col].cumsum() | |
| ch1[f"y{i}_lower"] = ch1[f"y{i}_upper"].shift(1).fillna(0) | |
| ch1[f"y{i}"] = ch1.apply(lambda x: (x[f"y{i}_upper"] + x[f"y{i}_lower"]) / 2, axis=1) | |
| final = final.merge( | |
| ch1[["categories", f"y{i}_upper", f"y{i}_lower", f"y{i}"]], on="categories" | |
| ) | |
| colors = { | |
| "Botox": "#72c6e8", | |
| "Filler": "#E41A37", | |
| "Laser Treatment": "#5c606d", | |
| "Skincare": "#12618F", | |
| "Hair Transplant": "#d9871b", | |
| "Others": "rgba(0,0,0,0.3)", | |
| } | |
| st.sidebar.header("🔍 施術フィルター") | |
| selected_categories = st.sidebar.multiselect( | |
| "表示する施術を選んでください:", | |
| options=df["categories"], | |
| default=df["categories"] | |
| ) | |
| fig = go.Figure() | |
| x = list(range(1, len(quarters) + 1)) | |
| x_rev = x[::-1] | |
| for category in selected_categories: | |
| ch1 = final.query("categories == @category") | |
| upper_col = [col for col in ch1.columns if "upper" in col] | |
| lower_col = [col for col in ch1.columns if "lower" in col] | |
| upper_data = ch1[upper_col].values.tolist()[0] | |
| lower_data = ch1[lower_col].values.tolist()[0] | |
| smooth_upper = pd.Series(upper_data).rolling(window=2, min_periods=1).mean().tolist() | |
| smooth_lower = pd.Series(lower_data).rolling(window=2, min_periods=1).mean().tolist() | |
| fig.add_trace( | |
| go.Scatter( | |
| x=x + x_rev, | |
| y=smooth_upper + smooth_lower[::-1], | |
| fill="toself", | |
| fillcolor=colors[category], | |
| opacity=0.7, | |
| line_color="rgba(0,0,0,0.2)", | |
| hoverinfo="none", | |
| showlegend=True, | |
| name=category | |
| ) | |
| ) | |
| fig.update_layout( | |
| xaxis_title="Quarter", | |
| yaxis_title="累積データ (人数)", | |
| xaxis=dict( | |
| tickmode="array", | |
| tickvals=x, | |
| ticktext=quarters | |
| ), | |
| plot_bgcolor="#f9f9f9", | |
| hovermode="x unified", | |
| height=600 | |
| ) | |
| st.title("各施術の累積データのリボングラフ") | |
| st.plotly_chart(fig, use_container_width=True) | |
| with tab5: | |
| import matplotlib.pyplot as plt | |
| import plotly.graph_objects as go | |
| nodes = [ | |
| "カウンセリング", "二重まぶた手術", "鼻整形", "顎矯正", "脂肪吸引", | |
| "フィラー施術", "ボトックス", "胸拡大", "リフティング", "ヘア移植", | |
| "回復管理", "追加ケア", "再施術" | |
| ] | |
| links = { | |
| 'source': [ | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 1, 2, 3, 4, 5, 6, 7, 8, 9, | |
| 2, 4, 6, | |
| 4, 7 | |
| ], | |
| 'target': [ | |
| 1, 2, 3, 4, 5, 6, 7, 8, 9, | |
| 10, 10, 10, 10, 10, 10, 10, 10, 10, | |
| 11, 11, 11, | |
| 12, 12 | |
| ], | |
| 'value': [ | |
| 1200, 950, 800, 700, 1800, 2200, 500, 670, 430, | |
| 1100, 900, 600, 700, 1700, 2100, 400, 600, 300, | |
| 300, 200, 100, | |
| 150, 100 | |
| ] | |
| } | |
| fig = go.Figure(go.Sankey( | |
| arrangement="snap", | |
| node=dict( | |
| pad=20, | |
| thickness=30, | |
| line=dict(color="black", width=0.5), | |
| label=nodes, | |
| color=[ | |
| '#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', | |
| '#e31a1c', '#fdbf6f', '#ff7f00', '#cab2d6', '#6a3d9a', | |
| '#ffff99', '#b15928', '#ffed6f' | |
| ] | |
| ), | |
| link=dict( | |
| source=links['source'], | |
| target=links['target'], | |
| value=links['value'], | |
| color='rgba(44, 160, 44, 0.4)' | |
| ) | |
| )) | |
| fig.update_layout( | |
| title_text="美容整形の施術プロセス Sankeyダイアグラム", | |
| font=dict(size=14), | |
| height=600 | |
| ) | |
| st.title("美容整形業界の主要な施術ルート(件数ベース)") | |
| st.plotly_chart(fig) | |
| with tab6: | |
| import plotly.graph_objects as go | |
| import plotly.graph_objects as go | |
| import streamlit as st | |
| # 🔹 データ定義 | |
| x = [ | |
| "2023年度 売上", | |
| "ボトックスの売上増加", | |
| "フィラー施術の売上減少", | |
| "レーザー治療の売上増加", | |
| "スキンケア商品の売上減少", | |
| "ヘア移植の売上増加", | |
| "その他の施術の影響", | |
| "2024年度 予測売上", | |
| ] | |
| y = [32000, 4500, -3000, 6000, -2000, 3500, 800, 42800] | |
| measure = [ | |
| "absolute", | |
| "relative", | |
| "relative", | |
| "relative", | |
| "relative", | |
| "relative", | |
| "relative", | |
| "total", | |
| ] | |
| fig = go.Figure( | |
| go.Waterfall( | |
| x=x, | |
| y=y, | |
| measure=measure, | |
| text=[f"{val:,}万円" for val in y], | |
| textposition="outside", | |
| decreasing={"marker": {"color": "#F44336"}}, | |
| increasing={"marker": {"color": "#4CAF50"}}, | |
| totals={"marker": {"color": "#2196F3"}}, | |
| connector={"line": {"color": "grey", "width": 2}}, | |
| constraintext="none" | |
| ) | |
| ) | |
| fig.update_layout( | |
| title={ | |
| "text": "📈 売上変動分析", | |
| "x": 0.5, | |
| "xanchor": "center", | |
| "yanchor": "top", | |
| "font": {"size": 24} | |
| }, | |
| yaxis=dict( | |
| title="売上 (万円)", | |
| tickfont=dict(size=18) | |
| ), | |
| xaxis=dict( | |
| tickfont=dict(size=16), | |
| tickangle=-30 | |
| ), | |
| height=800, | |
| waterfallgap=0.3, | |
| font=dict(size=18), | |
| plot_bgcolor="rgba(0,0,0,0)", | |
| margin=dict(l=40, r=40, t=60, b=80) | |
| ) | |
| st.title("📊 売上変動分析") | |
| st.plotly_chart(fig, use_container_width=True) |