StreamlitSample / app.py
medical-kiban's picture
視覚化サンプル追加
d48dc67
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)