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("
", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) st.markdown("
", 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)