1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Streamlitで図表まとめてZIP出力:CSV・Plotly・ネットワーク図を一括保存

Posted at

user:

Streamlit アプリで出力した図や表を、全てダウンロードするような機能をつけることはできますか?

assistant:

はい、Streamlitアプリで出力した図や表をすべてまとめてダウンロードできる機能を実装することは可能です。以下のような方法があります:


✅ 方法1:ZIPファイルで一括ダウンロード(推奨)

図(PNG, SVG, etc)や表(CSV, Excelなど)を一時的に保存し、それらをZIPファイルにまとめて st.download_button で提供する方法です。

📦 実装イメージ(概要):

import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import io
import zipfile

# 表の作成
df = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6]
})

# グラフの作成
fig, ax = plt.subplots()
ax.plot(df['A'], df['B'])
img_buffer = io.BytesIO()
fig.savefig(img_buffer, format='png')
img_buffer.seek(0)

# CSV出力
csv_buffer = io.StringIO()
df.to_csv(csv_buffer, index=False)
csv_buffer.seek(0)

# ZIPにまとめる
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
    zf.writestr("table.csv", csv_buffer.getvalue())
    zf.writestr("plot.png", img_buffer.read())
zip_buffer.seek(0)

# ダウンロードボタン
st.download_button(
    label="📦 結果を一括ダウンロード(ZIP)",
    data=zip_buffer,
    file_name="results.zip",
    mime="application/zip"
)

✅ 方法2:個別に st.download_button を使う(シンプル)

各表・図に対して個別にダウンロードボタンを表示することもできます。ただし、出力が多くなるとUIが煩雑になる可能性があります。


✅ 方法3:Excelの複数シート or PDFにまとめる(応用)

  • 複数の表を Excelのシートに分けて保存pandas.ExcelWriter を活用。
  • 図や表を PDFファイルにまとめるreportlabmatplotlib.backends.backend_pdf.PdfPages などで対応。

📝 補足

  • 一時ファイルではなく BytesIO などのメモリバッファを使うと、サーバー上のファイル管理が不要です。
  • matplotlib, plotly, altair いずれの図も BytesIO に保存可能です(形式に応じて変更)。

ご希望に応じて、Streamlitアプリ用の具体的なコードテンプレートもお作りします。どの形式の図や表を出力しているか教えていただければ、最適なコードを提案します。

user:

ありがとうございます。Plotlyで作画した図はどう保存できますか?

assistant:

どういたしまして!
Plotlyで作成した図を保存する方法について説明します。Streamlit上で扱うには、通常 plotly.graph_objects.FigureBytesIO に一時的に保存してから、st.download_button で提供する形になります。


✅ Plotly図をPNGとして保存する方法

🔧 事前準備(必要に応じて)

Plotlyの画像保存には kaleido が必要です。

pip install kaleido

🧪 例:Plotly図をPNGとしてStreamlitからダウンロード

import streamlit as st
import plotly.graph_objects as go
import io

# プロット作成
fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 1, 6], mode='lines+markers'))

# 図をBytesIOに保存(PNG形式)
img_bytes = fig.to_image(format="png", width=800, height=600, engine="kaleido")
img_buffer = io.BytesIO(img_bytes)

# ダウンロードボタン
st.download_button(
    label="📷 Plotly図をダウンロード(PNG)",
    data=img_buffer,
    file_name="plotly_figure.png",
    mime="image/png"
)

# Streamlitに図も表示
st.plotly_chart(fig)

✅ 他の保存形式も対応可能

形式 format= の値 MIMEタイプ
PNG "png" "image/png"
JPEG "jpeg" "image/jpeg"
SVG "svg" "image/svg+xml"
PDF "pdf" "application/pdf"
# 例: SVGで保存
img_bytes = fig.to_image(format="svg")

✅ ZIPに複数Plotly図を保存する(図が複数ある場合)

import zipfile

# 複数のPlotly図を保存してZIP化
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
    for i in range(3):
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=[1, 2, 3], y=[i, i+1, i+2]))
        img_bytes = fig.to_image(format="png")
        zf.writestr(f"plot_{i+1}.png", img_bytes)
zip_buffer.seek(0)

st.download_button("📦 すべての図をZIPでダウンロード", zip_buffer, "all_plots.zip", "application/zip")

📝 注意点

  • fig.to_image() を使うには kaleidoがインストールされている必要があります。
  • Streamlit Cloudでも requirements.txtkaleido を記載しておくことで動作します。

必要であれば、表と図をまとめてZIPで出力するサンプルコードも作成しますので、お気軽にどうぞ!

user:

plotly の図を保存するときに、width=800, height=600のように指定せず、もともと設定してあるwidthとheightを使うことはできますか?

assistant:

はい、もともとPlotly図 (fig) に設定されている widthheight を使って to_image() で保存することは可能です。

ただし注意点があります:


✅ 結論:fig.to_image() は明示的に width / height を指定しないと、既存の layout からは自動で引き継がれません

Plotlyの fig.layout.widthfig.layout.height に値が入っていても、to_image() はデフォルトでそれを参照しません。
そのため、必要であれば以下のように自分で取得して渡す必要があります。


✅ 実装例:もともと設定された width / height を使う

import plotly.graph_objects as go
import streamlit as st
import io

# width と height を layout に設定しておく
fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]))
fig.update_layout(width=700, height=500)

# layout から width / height を取得
width = fig.layout.width or 800  # デフォルト fallback も設定
height = fig.layout.height or 600

# 画像保存
img_bytes = fig.to_image(format="png", width=width, height=height, engine="kaleido")
img_buffer = io.BytesIO(img_bytes)

# Streamlitに表示
st.plotly_chart(fig)
st.download_button("ダウンロード(layoutサイズ)", data=img_buffer, file_name="figure.png", mime="image/png")

✅ ポイント

  • fig.layout.widthfig.layout.heightNone の可能性があるので、or でデフォルト値を入れておくと安全です。
  • to_image() を呼ぶときに widthheight を指定しなかった場合、Plotlyは 独自のデフォルト(700x450) を使用します。

必要であれば、複数図をこの方法で一括ダウンロードする例も用意します!

user:

ありがとうございます。csvについて、次のような関数を自作しましたが、この要領で、plotlyのfigureを入れたらファイル名とバッファーの組を返す関数を書いてください。

def get_buffer_for_csv(file_name, df):
    csv_buffer = io.StringIO()
    df.to_csv(csv_buffer, index=False)
    csv_buffer.seek(0)
    return [file_name, csv_buffer.seek)

assistant:

どういたしまして!
ご提示の get_buffer_for_csv 関数と同じ形式で、PlotlyのFigureを画像ファイルとして保存し、ファイル名とバッファーの組を返す関数を以下に示します。


✅ 目的に沿った関数(Plotly用)

import io

def get_buffer_for_plotly(file_name, fig, format="png"):
    """
    Plotly Figureを指定形式でバッファに保存し、[ファイル名, バッファ] を返す。
    
    Parameters:
        file_name (str): 保存するファイル名(例: 'plot.png')
        fig (go.Figure): PlotlyのFigureオブジェクト
        format (str): 保存形式('png', 'svg', 'jpeg', 'pdf' など)
    
    Returns:
        list: [file_name, io.BytesIOオブジェクト]
    """
    img_bytes = fig.to_image(format=format)
    img_buffer = io.BytesIO(img_bytes)
    img_buffer.seek(0)
    return [file_name, img_buffer]

✅ CSV版も修正すると良い点があります

csv_buffer.seek ではなく csv_buffer を返す必要があります。以下のように修正するのが正しいです:

import io

def get_buffer_for_csv(file_name, df):
    csv_buffer = io.StringIO()
    df.to_csv(csv_buffer, index=False)
    csv_buffer.seek(0)
    return [file_name, csv_buffer]

✅ 使用例(ZIPにまとめるなど)

import zipfile

buffers = []

# CSV
buffers.append(get_buffer_for_csv("data.csv", df))

# Plotly
buffers.append(get_buffer_for_plotly("plot.png", fig))

# ZIPにまとめる
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
    for file_name, buffer in buffers:
        zf.writestr(file_name, buffer.getvalue() if isinstance(buffer, io.StringIO) else buffer.read())
zip_buffer.seek(0)

ご希望に応じて、この関数群を使って「表+図をまとめてダウンロードするStreamlitアプリ」のテンプレートもご用意できます。必要ですか?

user:

ありがとうございます。get_buffer_for_plotly を、もともと設定された width / height を使う仕様に変更してください。

assistant:

こちらこそありがとうございます!
ご要望に応じて、Plotlyの layout.width / layout.height を自動的に取得して to_image() に渡すようにした関数に書き換えます。


✅ 修正版:get_buffer_for_plotly(layoutサイズを使用)

import io

def get_buffer_for_plotly(file_name, fig, format="png"):
    """
    Plotly Figureを指定形式でバッファに保存し、[ファイル名, バッファ] を返す。
    layout に設定された width / height を使う(未設定ならデフォルト使用)。

    Parameters:
        file_name (str): 保存するファイル名(例: 'plot.png')
        fig (go.Figure): PlotlyのFigureオブジェクト
        format (str): 保存形式('png', 'svg', 'jpeg', 'pdf' など)

    Returns:
        list: [file_name, io.BytesIOオブジェクト]
    """
    # layout から width / height を取得(未設定時は None)
    width = fig.layout.width
    height = fig.layout.height

    # キーワード引数を組み立てる(Noneは渡さない)
    kwargs = {"format": format}
    if width:
        kwargs["width"] = width
    if height:
        kwargs["height"] = height

    # 画像化してバッファに保存
    img_bytes = fig.to_image(**kwargs)
    img_buffer = io.BytesIO(img_bytes)
    img_buffer.seek(0)
    return [file_name, img_buffer]

✅ この関数の特徴

  • layout.width / layout.height に値があればそれを使い、なければ to_image() のデフォルト(700×450)が使用されます。
  • None を明示的に渡すとエラーになるため、辞書で条件付きにしています。

✅ 使用例(前と同じ)

buffers.append(get_buffer_for_plotly("figure1.png", fig))

ご希望であれば、CSV・Plotly図を複数まとめてZIPでダウンロードするStreamlitテンプレートもご提供します。必要であればお申し付けください。

user:

CSV・Plotly図を複数まとめてZIPでダウンロードするStreamlitテンプレートを、お願いします。

assistant:

了解しました!
以下は、複数のCSVとPlotly図を一括でZIPファイルとしてダウンロードできるStreamlitアプリのテンプレートです。


✅ Streamlitテンプレートコード(CSV + Plotly図 → ZIP)

import streamlit as st
import pandas as pd
import plotly.graph_objects as go
import io
import zipfile

# --- ヘルパー関数定義 ---
def get_buffer_for_csv(file_name, df):
    csv_buffer = io.StringIO()
    df.to_csv(csv_buffer, index=False)
    csv_buffer.seek(0)
    return [file_name, csv_buffer]

def get_buffer_for_plotly(file_name, fig, format="png"):
    width = fig.layout.width
    height = fig.layout.height
    kwargs = {"format": format}
    if width: kwargs["width"] = width
    if height: kwargs["height"] = height
    img_bytes = fig.to_image(**kwargs)
    img_buffer = io.BytesIO(img_bytes)
    img_buffer.seek(0)
    return [file_name, img_buffer]

# --- サンプルデータ作成 ---
df1 = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
df2 = pd.DataFrame({"X": [10, 20, 30], "Y": [7, 8, 9]})

fig1 = go.Figure()
fig1.add_trace(go.Scatter(x=df1["A"], y=df1["B"]))
fig1.update_layout(title="Figure 1", width=600, height=400)

fig2 = go.Figure()
fig2.add_trace(go.Bar(x=df2["X"], y=df2["Y"]))
fig2.update_layout(title="Figure 2", width=500, height=300)

# --- 表示 ---
st.write("### 表とグラフの表示")
st.dataframe(df1)
st.plotly_chart(fig1)
st.dataframe(df2)
st.plotly_chart(fig2)

# --- バッファをリストにまとめる ---
buffers = []
buffers.append(get_buffer_for_csv("data1.csv", df1))
buffers.append(get_buffer_for_csv("data2.csv", df2))
buffers.append(get_buffer_for_plotly("figure1.png", fig1))
buffers.append(get_buffer_for_plotly("figure2.png", fig2))

# --- ZIPにまとめる ---
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w") as zf:
    for file_name, buffer in buffers:
        if isinstance(buffer, io.StringIO):
            zf.writestr(file_name, buffer.getvalue())
        else:  # BytesIO
            zf.writestr(file_name, buffer.read())
zip_buffer.seek(0)

# --- ダウンロードボタン ---
st.download_button(
    label="📦 結果をZIPで一括ダウンロード",
    data=zip_buffer,
    file_name="all_results.zip",
    mime="application/zip"
)

✅ 特徴

  • CSVは StringIO、画像は BytesIO で処理。
  • layout.width / height に従ってPlotly図を保存。
  • ファイル名は "data1.csv", "figure1.png" など。
  • to_image() を使っているため、事前に kaleido のインストールが必要です:
pip install kaleido

✅ 拡張アイデア(必要であればご支援可能です)

  • Excelの複数シートでまとめる
  • PDFに図をレイアウトして出力
  • ファイル名をユーザーが入力可能にする

ご希望があれば、これを使ったブログ記事テンプレートも作成できます!

user:

ありがとうございます。networkx で描いたネットワーク図についても、現在このようにしてダウンロードボタンを設置しているのですが、上記のZIPファイルに含めることはできますか?

with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as tmp_file:
        path = tmp_file.name
        net.save_graph(path)
        with open(path, "r", encoding="utf-8") as f:
            html_content = f.read()
        st.components.v1.html(html_content, height=700, scrolling=True)

    with open(path, "rb") as f:
        btn = st.download_button(
            label="ダウンロード",
            data=f,
            file_name=file_name,
            mime="text/html",
            key=f"{file_name}_{random.random()}",
        )

assistant:

はい、networkx + pyvis で生成した .html ファイルをZIPに含めることは可能です。
現在の処理で .html ファイルを一時保存して表示・ダウンロードしていますが、同じ内容を read() して zipfile に追加すればOKです。


✅ 修正方針

以下を行えば ZIP に含められます:

  1. .html ファイルを保存して html_content = f.read() とする
  2. その文字列(または BytesIO)を ZIP の writestr() で書き込む

✅ 拡張用コード(上記テンプレートに追加)

from pyvis.network import Network
import networkx as nx
import tempfile
import random

# --- pyvis ネットワーク作成 ---
G = nx.erdos_renyi_graph(n=10, p=0.3, seed=42)
net = Network(notebook=False)
net.from_nx(G)

# --- 一時ファイルに保存(この部分は表示用と共通) ---
with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as tmp_file:
    path = tmp_file.name
    net.save_graph(path)

# --- 表示(そのまま) ---
with open(path, "r", encoding="utf-8") as f:
    html_content = f.read()
st.components.v1.html(html_content, height=700, scrolling=True)

# --- ダウンロードボタン(そのまま) ---
with open(path, "rb") as f:
    st.download_button(
        label="ネットワーク図をHTMLでダウンロード",
        data=f,
        file_name="network.html",
        mime="text/html",
        key=f"network_{random.random()}"
    )

# --- ZIPに追加用:バッファとして格納 ---
with open(path, "r", encoding="utf-8") as f:
    network_html_str = f.read()
buffers.append(["network.html", io.StringIO(network_html_str)])

✅ 最終的にZIP化部分(そのまま使えます)

zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w") as zf:
    for file_name, buffer in buffers:
        if isinstance(buffer, io.StringIO):
            zf.writestr(file_name, buffer.getvalue())
        else:  # BytesIO
            zf.writestr(file_name, buffer.read())
zip_buffer.seek(0)

st.download_button(
    label="📦 全出力をZIPで一括ダウンロード",
    data=zip_buffer,
    file_name="all_results.zip",
    mime="application/zip"
)

✅ ポイントまとめ

  • pyvissave_graph().html を生成するので、それを read() して StringIO に入れればZIPに含められる。
  • StringIO.writestr() に渡すことでZIPに文字ファイルとして書き込み可能。
  • 一時ファイルで保存 → 表示・個別DL → ZIPに含める、という流れは問題なし。

必要であれば、「CSV+図+ネットワーク図を含むZIP出力アプリ」の完成版もひとつの関数でまとめます。お気軽にどうぞ。

user:

ありがとうございます。get_buffer_for_csv や get_buffer_for_plotly のような関数にしてください。

assistant:

承知しました!
以下に、get_buffer_for_pyvis_html 関数として、get_buffer_for_csvget_buffer_for_plotly と同様の形式で定義したものを示します。


get_buffer_for_pyvis_html 関数

import io
import tempfile

def get_buffer_for_pyvis_html(file_name, net):
    """
    Pyvisネットワーク(Networkオブジェクト)をHTMLとして保存し、
    [ファイル名, io.StringIOオブジェクト] の形式で返す。

    Parameters:
        file_name (str): 保存するHTMLファイル名(例: 'network.html')
        net (pyvis.network.Network): PyvisのNetworkオブジェクト

    Returns:
        list: [file_name, io.StringIO]
    """
    # 一時ファイルにHTMLとして保存
    with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as tmp_file:
        path = tmp_file.name
        net.save_graph(path)

    # HTML内容を読み込み、StringIO に格納
    with open(path, "r", encoding="utf-8") as f:
        html_content = f.read()
    html_buffer = io.StringIO(html_content)
    html_buffer.seek(0)

    return [file_name, html_buffer]

✅ 他の関数と統一形式で使えます

buffers = []
buffers.append(get_buffer_for_csv("data.csv", df))
buffers.append(get_buffer_for_plotly("plot.png", fig))
buffers.append(get_buffer_for_pyvis_html("network.html", net))

✅ ZIP出力にもそのまま使えます

with zipfile.ZipFile(zip_buffer, "w") as zf:
    for file_name, buffer in buffers:
        if isinstance(buffer, io.StringIO):
            zf.writestr(file_name, buffer.getvalue())
        else:  # io.BytesIO
            zf.writestr(file_name, buffer.read())

必要であれば、networkx.Graphpyvis.Network への変換や、レイアウト指定の方法もご案内できます。お気軽にお知らせください!

user:

ありがとうございます!今回の会話をブログにまとめたいので、タイトル案をいくつか提案してください。

assistant:

こちらこそありがとうございます!
今回の内容は StreamlitアプリでCSV・Plotly図・Pyvisネットワーク図をZIP一括ダウンロード できる便利機能に関する実践的な話でしたね。以下にタイトル案をいくつか提案します:


🔷 実用系タイトル案

  1. 「Streamlitで図表まとめてZIP出力:CSV・Plotly・ネットワーク図を一括保存」
  2. 「CSVも図もZIPで一括!Streamlitで出力ファイルをスマートに管理する方法」
  3. 「Streamlit × ZIP出力:CSV・グラフ・ネットワーク図をまるごと保存するアプリ構築術」
  4. 「Plotlyやpyvisの図もOK!Streamlitで複数ファイルをZIP一括ダウンロード」
  5. 「Streamlitで複数データを一気にダウンロード:CSV・PNG・HTML対応テンプレート」

🔶 開発者目線・技術寄りタイトル案

  1. 「get_buffer関数でスマート出力:Streamlit × ZIPファイル構築のベストプラクティス」
  2. 「Streamlit × ファイル保存の設計パターン:図・表・HTMLをまとめて出力する」
  3. 「Pyvis × Plotly × Pandas をZIP化!Streamlitアプリでデータ出力を最適化」
  4. 「Streamlitの出力設計:図表をZIPにまとめるための関数設計と実装例」
  5. 「Streamlitで実現する出力自動化:CSVもグラフもネットワークもワンクリック保存」

ご希望があれば、記事の導入文や構成案もご提案できます。
お気軽にどうぞ!

user:

「Streamlitで図表まとめてZIP出力:CSV・Plotly・ネットワーク図を一括保存」でいきたいと思います。最後に、まとめを書いてください。

assistant:

素晴らしいタイトルのご選定ありがとうございます!
以下に、記事の締めくくりとしてふさわしい「まとめ」をご提案します:


✅ まとめ

本記事では、Streamlitアプリで複数の出力ファイル(CSV・Plotly図・ネットワーク図)をZIP形式で一括ダウンロードできる機能を実装する方法を紹介しました。

具体的には以下のポイントを解説しました:

  • get_buffer_for_csvget_buffer_for_plotly のような再利用可能な関数設計
  • pyvis を使ったNetworkXグラフのHTML出力とZIPへの組み込み方法
  • StringIO / BytesIO を使った柔軟なファイル操作
  • zipfile モジュールでの一括アーカイブと st.download_button による出力

このように、Streamlitアプリにダウンロード機能を整理して実装することで、ユーザーは可視化やデータ分析の結果を簡単に持ち帰ることが可能になります。教育用アプリやデータレポートツール、プロトタイピング用途にも非常に有用です。


今後は、以下のような拡張も視野に入れるとさらに実用性が高まります:

  • 複数CSVを1つのExcelファイルにまとめて保存
  • 図表をPDFレポート化
  • ファイル選択・命名のインターフェース追加

Streamlitでの出力管理を一歩進めたい方は、ぜひ本記事を参考にカスタマイズしてみてください!


ご希望があれば、冒頭文や導入ストーリーの草案も作成できますので、お気軽にお申し付けください。

user:

ありがとうございました。今日はこれで大丈夫です。今後もよろしくお願いします!

assistant:

こちらこそ、ありがとうございました!
Streamlitやデータ可視化の話題、とても楽しくご一緒できました。
またいつでもご相談ください。今後ともどうぞよろしくお願いいたします!😊

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?