こんにちは、木川です。
データ分析の結果をチームに共有する際、「Excelファイルを添付したメール」や「静的なグラフ画像」では、相手が自由に探索できず不便ではありませんか?
ここでは、Pythonのライブラリ「Streamlit」を使って、誰でもブラウザで操作できるインタラクティブなデータ分析ダッシュボードを作成する方法を、実際に動くコード付きで解説します。
それではまいりましょう!
この記事で作るもの
売上データを可視化・分析できるダッシュボードを作成します。
主要な機能は以下です。
- 売上トレンドの可視化 (折れ線グラフ、棒グラフなど)
- 期間や商品カテゴリでのフィルタリング
- 主要指標(KPI)の表示
- CSVデータのアップロード機能
- 分析結果のダウンロード
Streamlitとは
Streamlitは、Pythonを使用して簡単にインタラクティブなWebアプリケーションを作成できるオープンソースのフレームワークです。
特徴は以下です。
- HTMLやJavaScriptの知識不要
- Pythonコードだけでインタラクティブなアプリを作成
- ローカルでもクラウドでも簡単にデプロイ可能
- データ分析ライブラリ(pandas、matplotlib等)との相性抜群
環境構築
必要なライブラリのインストール
まずはお決まりの流れでPython開発環境を作成します。以下をインストールしましょう。
pip install streamlit pandas plotly openpyxlバージョン確認
Streamlitのバージョンを確認します。ターミナルで以下のコマンドを実行し、streamlit version 1.X.Xが表示されればOKです。
streamlit --version実装
実際に実装していきます。本記事では、後で差分を比較できるように、各セクションで各機能を追加する度に、新しいファイルを作成します。そのため、後半に進むにつれて、例にあげるコードが長くなっていきますが、その分コピー&ペーストでそのまま動くようになっています。
サンプルデータの準備
プロジェクトのルートディレクトリに「sample_data_generator.py」というサンプルファイルを作成し、以下のようなデータを作成します。
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
# サンプルデータ生成
np.random.seed(42)
date_range = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
data = {
'日付': date_range,
'売上': np.random.randint(50000, 200000, len(date_range)),
'商品カテゴリ': np.random.choice(['電子機器', '家具', '食品', '衣類'], len(date_range)),
'地域': np.random.choice(['東京', '大阪', '名古屋', '福岡'], len(date_range)),
'顧客数': np.random.randint(10, 100, len(date_range))
}
df = pd.DataFrame(data)
df.to_csv('sales_data.csv', index=False, encoding='utf-8-sig')
print("サンプルデータを sales_data.csv として保存しました")作成できたら、ターミナルでルートディレクトリに移動し、上記のファイルを実行しましょう。
python sample_data_generator.pyすると、以下のようなサンプルデータのCSVファイルが生成されます。

基本的なStreamlitアプリの作成
まずはシンプルな形から作成します。「app_basic.py」というファイルを作成し、以下のコードを書きます。
# app_basic.py
import streamlit as st
import pandas as pd
# ページ設定
st.set_page_config(
page_title="売上分析ダッシュボード",
page_icon="📊",
layout="wide"
)
# タイトル
st.title("📊 売上分析ダッシュボード")
# データ読み込み
@st.cache_data # キャッシュして高速化
def load_data():
return pd.read_csv('sales_data.csv', parse_dates=['日付'])
df = load_data()
# データ表示
st.subheader("データプレビュー")
st.dataframe(df.head(10))
# 基本統計量
st.subheader("基本統計量")
st.write(df.describe())この状態でアプリを実行してみます。
streamlit run app_basic.pyすると、以下のような画面が立ち上がります。


この実装のポイントは、以下です。
インタラクティブ機能の追加
フィルタリング機能を追加して、ユーザーがデータを絞り込めるようにします。「app_interactive.py」というファイルを作成し、以下のコードを書きます。
# app_interactive.py
import streamlit as st
import pandas as pd
import plotly.express as px
st.set_page_config(page_title="売上分析ダッシュボード", page_icon="📊", layout="wide")
@st.cache_data
def load_data():
df = pd.read_csv('sales_data.csv', parse_dates=['日付'])
return df
df = load_data()
# サイドバーでフィルター
st.sidebar.header("🔍 フィルター設定")
# 日付範囲選択
date_range = st.sidebar.date_input(
"期間を選択",
value=(df['日付'].min(), df['日付'].max()),
min_value=df['日付'].min(),
max_value=df['日付'].max()
)
# カテゴリ選択
categories = st.sidebar.multiselect(
"商品カテゴリ",
options=df['商品カテゴリ'].unique(),
default=df['商品カテゴリ'].unique()
)
# 地域選択
regions = st.sidebar.multiselect(
"地域",
options=df['地域'].unique(),
default=df['地域'].unique()
)
# データフィルタリング
if len(date_range) == 2:
mask = (
(df['日付'] >= pd.Timestamp(date_range[0])) &
(df['日付'] <= pd.Timestamp(date_range[1])) &
(df['商品カテゴリ'].isin(categories)) &
(df['地域'].isin(regions))
)
filtered_df = df[mask]
else:
filtered_df = df
# メインエリア
st.title("📊 売上分析ダッシュボード")
# KPI表示
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("総売上", f"¥{filtered_df['売上'].sum():,.0f}")
with col2:
st.metric("平均売上", f"¥{filtered_df['売上'].mean():,.0f}")
with col3:
st.metric("総顧客数", f"{filtered_df['顧客数'].sum():,}")
with col4:
st.metric("データ件数", f"{len(filtered_df):,}")
# グラフ表示
st.subheader("📈 売上トレンド")
daily_sales = filtered_df.groupby('日付')['売上'].sum().reset_index()
fig = px.line(daily_sales, x='日付', y='売上', title='日次売上推移')
st.plotly_chart(fig, use_container_width=True)
# カテゴリ別売上
st.subheader("🏷️ カテゴリ別売上")
category_sales = filtered_df.groupby('商品カテゴリ')['売上'].sum().reset_index()
fig2 = px.bar(category_sales, x='商品カテゴリ', y='売上', title='商品カテゴリ別売上')
st.plotly_chart(fig2, use_container_width=True)同じようにアプリを実行します。
streamlit run app_interactive.pyすると、今回は以下のような画面になっています。


この実装のポイントは、以下です。
完全版ダッシュボード
それでは、今回のメインとなる、完全版のダッシュボードを書いていきます。「app_complete.py」というファイルを作成し、以下のコードを書きます。
# app_complete.py
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from io import BytesIO
# ページ設定
st.set_page_config(
page_title="売上分析ダッシュボード",
page_icon="📊",
layout="wide",
initial_sidebar_state="expanded"
)
# カスタムCSS
st.markdown("""
<style>
.main {
padding: 0rem 1rem;
}
.stMetric {
background-color: #f0f2f6;
padding: 15px;
border-radius: 10px;
}
</style>
""", unsafe_allow_html=True)
# データ読み込み関数
@st.cache_data
def load_data(file):
if file is not None:
df = pd.read_csv(file, parse_dates=['日付'])
else:
df = pd.read_csv('sales_data.csv', parse_dates=['日付'])
return df
# Excel変換関数
def to_excel(df):
output = BytesIO()
with pd.ExcelWriter(output, engine='openpyxl') as writer:
df.to_excel(writer, index=False, sheet_name='分析結果')
return output.getvalue()
# サイドバー
st.sidebar.title("⚙️ 設定")
# ファイルアップロード
uploaded_file = st.sidebar.file_uploader(
"CSVファイルをアップロード",
type=['csv'],
help="日付,売上,商品カテゴリ,地域,顧客数の列が必要です"
)
# データ読み込み
df = load_data(uploaded_file)
# サイドバーフィルター
st.sidebar.header("🔍 フィルター")
# 日付範囲
min_date = df['日付'].min().date()
max_date = df['日付'].max().date()
date_range = st.sidebar.date_input(
"期間",
value=(min_date, max_date),
min_value=min_date,
max_value=max_date
)
# カテゴリ選択
all_categories = df['商品カテゴリ'].unique().tolist()
selected_categories = st.sidebar.multiselect(
"商品カテゴリ",
options=all_categories,
default=all_categories
)
# 地域選択
all_regions = df['地域'].unique().tolist()
selected_regions = st.sidebar.multiselect(
"地域",
options=all_regions,
default=all_regions
)
# 売上範囲フィルター
min_sales, max_sales = int(df['売上'].min()), int(df['売上'].max())
sales_range = st.sidebar.slider(
"売上範囲",
min_value=min_sales,
max_value=max_sales,
value=(min_sales, max_sales)
)
# データフィルタリング
if len(date_range) == 2:
mask = (
(df['日付'] >= pd.Timestamp(date_range[0])) &
(df['日付'] <= pd.Timestamp(date_range[1])) &
(df['商品カテゴリ'].isin(selected_categories)) &
(df['地域'].isin(selected_regions)) &
(df['売上'] >= sales_range[0]) &
(df['売上'] <= sales_range[1])
)
filtered_df = df[mask].copy()
else:
filtered_df = df.copy()
# メインエリア
st.title("📊 売上分析ダッシュボード")
st.markdown("---")
# KPIメトリクス
col1, col2, col3, col4, col5 = st.columns(5)
total_sales = filtered_df['売上'].sum()
avg_sales = filtered_df['売上'].mean()
total_customers = filtered_df['顧客数'].sum()
avg_customers = filtered_df['顧客数'].mean()
data_count = len(filtered_df)
with col1:
st.metric(
"総売上",
f"¥{total_sales:,.0f}",
delta=f"{(total_sales/df['売上'].sum()*100):.1f}% of total"
)
with col2:
st.metric(
"平均売上",
f"¥{avg_sales:,.0f}"
)
with col3:
st.metric(
"総顧客数",
f"{total_customers:,}"
)
with col4:
st.metric(
"平均顧客数",
f"{avg_customers:.1f}"
)
with col5:
st.metric(
"データ件数",
f"{data_count:,}"
)
st.markdown("---")
# タブで情報を整理
tab1, tab2, tab3, tab4 = st.tabs(["📈 トレンド分析", "🏷️ カテゴリ分析", "🗺️ 地域分析", "📋 詳細データ"])
with tab1:
st.subheader("売上トレンド")
# 日次集計
daily_sales = filtered_df.groupby('日付').agg({
'売上': 'sum',
'顧客数': 'sum'
}).reset_index()
# 折れ線グラフ
fig1 = go.Figure()
fig1.add_trace(go.Scatter(
x=daily_sales['日付'],
y=daily_sales['売上'],
mode='lines',
name='売上',
line=dict(color='#1f77b4', width=2)
))
fig1.update_layout(
title='日次売上推移',
xaxis_title='日付',
yaxis_title='売上(円)',
hovermode='x unified'
)
st.plotly_chart(fig1, use_container_width=True)
# 移動平均
window = st.slider("移動平均日数", 1, 30, 7)
daily_sales['移動平均'] = daily_sales['売上'].rolling(window=window).mean()
fig2 = px.line(daily_sales, x='日付', y=['売上', '移動平均'],
title=f'{window}日移動平均')
st.plotly_chart(fig2, use_container_width=True)
with tab2:
st.subheader("商品カテゴリ別分析")
col1, col2 = st.columns(2)
with col1:
# カテゴリ別売上
category_sales = filtered_df.groupby('商品カテゴリ')['売上'].sum().reset_index()
fig3 = px.bar(
category_sales,
x='商品カテゴリ',
y='売上',
title='カテゴリ別売上',
color='売上',
color_continuous_scale='Blues'
)
st.plotly_chart(fig3, use_container_width=True)
with col2:
# 円グラフ
fig4 = px.pie(
category_sales,
values='売上',
names='商品カテゴリ',
title='カテゴリ別売上構成比'
)
st.plotly_chart(fig4, use_container_width=True)
# カテゴリ×地域のヒートマップ
st.subheader("カテゴリ×地域 売上ヒートマップ")
pivot_data = filtered_df.pivot_table(
values='売上',
index='商品カテゴリ',
columns='地域',
aggfunc='sum',
fill_value=0
)
fig5 = px.imshow(
pivot_data,
labels=dict(x="地域", y="商品カテゴリ", color="売上"),
x=pivot_data.columns,
y=pivot_data.index,
color_continuous_scale='RdYlGn',
title='カテゴリ×地域の売上分布'
)
st.plotly_chart(fig5, use_container_width=True)
with tab3:
st.subheader("地域別分析")
col1, col2 = st.columns(2)
with col1:
# 地域別売上
region_sales = filtered_df.groupby('地域')['売上'].sum().reset_index()
fig6 = px.bar(
region_sales,
x='地域',
y='売上',
title='地域別売上',
color='売上',
color_continuous_scale='Greens'
)
st.plotly_chart(fig6, use_container_width=True)
with col2:
# 地域別顧客数
region_customers = filtered_df.groupby('地域')['顧客数'].sum().reset_index()
fig7 = px.bar(
region_customers,
x='地域',
y='顧客数',
title='地域別顧客数',
color='顧客数',
color_continuous_scale='Oranges'
)
st.plotly_chart(fig7, use_container_width=True)
with tab4:
st.subheader("詳細データ")
# 検索機能
search_term = st.text_input("🔍 検索(商品カテゴリまたは地域)")
if search_term:
display_df = filtered_df[
(filtered_df['商品カテゴリ'].str.contains(search_term, case=False)) |
(filtered_df['地域'].str.contains(search_term, case=False))
]
else:
display_df = filtered_df
# データ表示
st.dataframe(
display_df.sort_values('日付', ascending=False),
use_container_width=True,
height=400
)
# ダウンロードボタン
col1, col2 = st.columns(2)
with col1:
csv = display_df.to_csv(index=False).encode('utf-8-sig')
st.download_button(
label="📥 CSVダウンロード",
data=csv,
file_name=f"filtered_data_{pd.Timestamp.now().strftime('%Y%m%d')}.csv",
mime="text/csv"
)
with col2:
excel_data = to_excel(display_df)
st.download_button(
label="📥 Excelダウンロード",
data=excel_data,
file_name=f"filtered_data_{pd.Timestamp.now().strftime('%Y%m%d')}.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
# フッター
st.markdown("---")
st.markdown(
"""
<div style='text-align: center'>
<p>Powered by Streamlit | データ件数: {} | 最終更新: {}</p>
</div>
""".format(len(df), pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')),
unsafe_allow_html=True
)コチラも同様に、アプリを実行します。
streamlit run app_complete.pyすると、このようなアプリが立ち上がります。


タブ切り替え



今回の実装のポイントは以下です。
まとめ
本記事では、Streamlitを使ってデータ分析ダッシュボードを作成する方法を解説しました。普段プログラミングをする方であれば、「かなりコード量が少ないな」と感じたのではないでしょうか?
このように、Streamlitを使えば、ものの数時間で本格的なダッシュボードが作れます。ぜひ活用してみてください!
最後まで読んでいただき、ありがとうございました!



コメント