見出し画像

クリック3回でグラフ爆誕!CSV/Excelを即座に可視化するPython GUIスクリプト #12

Pythonでデータ解析や可視化を繰り返す中で、「毎回Plotlyのコードを手で書くのが面倒」と感じていました。そこで、ファイルを選ぶだけで瞬時にグラフ化できる便利なPythonスクリプトを作成しました。今回は、そのスクリプトの構成や使い方を共有します。


なぜこのツールを作ったのか?


データ分析やレポート作成のたびに毎回Plotlyでコードを書き直すのは非効率です。特に以下の作業がストレスでした。

  • CSVやExcelをいちいち読み込むコードを書く

  • X軸やY軸の設定を都度調整

  • グラフタイプを手動で変更

「ファイルを選ぶだけでサクッとグラフ化したい!」そんな欲望を叶えるために開発しました。


コンセプトとゴール

本ツールの目標は以下の通りです。

  • GUI(Tkinter)でファイルを直感的に選択

  • pandasでCSV/Excelを柔軟に読み込み

  • Plotlyで折れ線/散布図を瞬時に表示

  • キャッシュ機能で巨大ファイルも高速再読込


使ったライブラリと環境

  • Python:3.10以上

  • pandas:データフレーム操作

  • Tkinter:ファイル選択GUI

  • plotly:インタラクティブなグラフ描画

  • pickle:データキャッシュ


スクリプトの動作イメージ

スクリプトは次の手順で動作します。

  1. ファイル選択ダイアログ(CSV/Excelを選択)

  2. pandasでデータフレーム生成(自動キャッシュ保存)

  3. Plotlyで複数軸のグラフを即座に表示(X/Y軸自由切替、折れ線/散布図切替可能)



コードのポイント解説(3つのポイント)

① キャッシュ機能

  • 一度読み込んだファイルをpickleで保存

  • 再読み込み時はキャッシュを選択可

② GUI操作(Tkinter)

  • 「filedialog」でファイルを選ぶだけ

  • 非エンジニアでも簡単操作

③ Plotlyで柔軟可視化

  • X軸、Y軸の変更がドロップダウンで可能

  • 「折れ線」と「散布図」をワンクリックで切り替え



実際に使ってみた感想

このツールを使うようになり、データ分析の下準備が大幅に短縮されました。特にキャッシュ機能のおかげで、数十万行の巨大CSVファイルでも再読み込みが一瞬で終わります。

まとめ

手間がかかる作業はスクリプトで自動化することで、大幅な効率化につながります。本記事のスクリプトをベースに、皆さんも自分だけの自動化ツールを作ってみてはいかがでしょうか?

使ってみた感想や改善案など、お気軽にお寄せください!

作成スクリプト

#作成したスクリプト
import os
import pickle
import pandas as pd
from datetime import datetime

import tkinter as tk
from tkinter import filedialog, simpledialog, messagebox

import plotly.graph_objects as go

#データフレームの作成
# ▼ キャッシュファイルのパス
CACHE_PATH = "df_cache.pkl"

# ▼ Tk初期化(ウィンドウは表示しない)
root = tk.Tk()
root.withdraw()

# ▼ キャッシュを使うか確認(かつ元ファイル名表示)
use_cache = False
if os.path.exists(CACHE_PATH):
    with open(CACHE_PATH, "rb") as f:
        cache_obj = pickle.load(f)
    cached_path = cache_obj.get("file_path", "(不明)")
    cache_time = datetime.fromtimestamp(os.path.getmtime(CACHE_PATH))

    use_cache = messagebox.askyesno(
        title="キャッシュ確認",
        message=(
            f"前回のデータ({cache_time})があります。\n"
            f"元ファイル: {cached_path}\n\nキャッシュから読み込みますか?"
        )
    )

if use_cache:
    # キャッシュ読み込み
    df = cache_obj["df"]
    print("✅ キャッシュからDataFrameを読み込みました")
    print(f"📂 元ファイル: {cache_obj.get('file_path', '(不明)')}")
    print(f"📅 最終保存日時: {datetime.fromtimestamp(os.path.getmtime(CACHE_PATH))}")

else:
    # ファイル選択して読み込み
    file_path = filedialog.askopenfilename(
        title="CSVまたはExcelファイルを選択してください",
        filetypes=[("CSV files", "*.csv"), ("Excel files", "*.xlsx *.xls")]
    )

    if not file_path:
        print("⛔ ファイル選択がキャンセルされました。")
        exit()

    # ファイル形式に応じて読み込み
    if file_path.endswith(".csv"):
        df = pd.read_csv(file_path)
    elif file_path.endswith((".xlsx", ".xls")):
        df = pd.read_excel(file_path)
    else:
        messagebox.showerror("エラー", "対応していないファイル形式です")
        raise ValueError("対応していないファイル形式です")

    # 辞書でキャッシュ保存
    cache_obj = {
        "df": df,
        "file_path": file_path
    }
    with open(CACHE_PATH, "wb") as f:
        pickle.dump(cache_obj, f)

    print(f"📁 新規読み込み完了 → キャッシュ保存済: {CACHE_PATH}")
    print(f"📝 元ファイル: {file_path}") 

# ▼ カラム一覧取得
columns = df.columns.tolist()
x_init = columns[0] if len(columns) > 0 else None
y_candidates = [col for col in columns if col != x_init]

# ▼ Figure生成:Y列を全て表示
fig = go.Figure()
for y_col in y_candidates:
    fig.add_trace(go.Scatter(
        x=df[x_init],
        y=df[y_col],
        mode='lines', # 折れ線
        # mode='markers' # 散布図
        name=y_col,
        line=dict(width=2)
    ))

# ▼ X軸切替のドロップダウン
x_buttons = []
for x_col in columns:
    # すべてのY列について、Xを更新(Traceの数だけ繰り返し)
    x_args = {"x": [df[x_col] for _ in y_candidates]}
    layout_args = {"xaxis": {"title": x_col}}
    x_buttons.append(dict(label=x_col, method="update", args=[x_args, layout_args]))

# ▼ Layout
fig.update_layout(
    title="X軸切替 + 複数Y系列表示(凡例で制御可能)",
    xaxis=dict(title=x_init, tickfont=dict(size=12)),
    yaxis=dict(title="値", tickfont=dict(size=12)),
    legend=dict(font=dict(size=12)),
    margin=dict(l=40, r=40, t=60, b=40),
    template="simple_white",
    updatemenus=[
        dict(
            buttons=x_buttons,
            direction="down",
            x=0.1, y=1.2,
            xanchor="left", yanchor="top",
            showactive=True,
            pad={"r": 10}
        )
    ]
)

fig.show()

#指定データの可視化
# ▼ カラム一覧取得(自動)
columns = df.columns.tolist()
x_init = columns[0] if len(columns) > 0 else None
y_init = columns[1] if len(columns) > 1 else None

# ▼ Plotlyで折れ線グラフ(初期は x=0列, y=1列)
fig = go.Figure()
fig.add_trace(go.Scatter(
    x=df[x_init],
    y=df[y_init],
    mode='lines',  # 初期表示は折れ線グラフ
    name=f"{x_init} vs {y_init}",
    line=dict(width=2)
))

# ▼ X軸選択ドロップダウン
x_buttons = [
    dict(label=col, method='update',
         args=[{'x': [df[col]]},
               {'xaxis': {'title': col}}])
    for col in columns
]

# ▼ Y軸選択ドロップダウン
y_buttons = [
    dict(label=col, method='update',
         args=[{'y': [df[col]]},
               {'yaxis': {'title': col}}])
    for col in columns
]

# ▼ グラフタイプ切替のドロップダウン(折れ線 or 散布図)
graph_type_buttons = [
    dict(label="折れ線", method="restyle", args=[{"mode": "lines"}]),
    dict(label="散布図", method="restyle", args=[{"mode": "markers"}])
]

# ▼ レイアウト(X/Y軸のタイトルも初期化)
fig.update_layout(
    title="X/Y軸切替 & グラフタイプ切替(折れ線 / 散布図)",
    xaxis=dict(title=x_init, tickfont=dict(size=12)),
    yaxis=dict(title=y_init, tickfont=dict(size=12)),
    legend=dict(font=dict(size=12)),
    margin=dict(l=40, r=40, t=60, b=40),
    template="simple_white",
    updatemenus=[
        dict(
            buttons=x_buttons,
            direction="down",
            x=0.1, y=1.2,
            xanchor="left", yanchor="top",
            showactive=True,
            pad={"r": 10}
        ),
        dict(
            buttons=y_buttons,
            direction="down",
            x=0.4, y=1.2,
            xanchor="left", yanchor="top",
            showactive=True
        ),
        dict(
            buttons=graph_type_buttons,
            direction="down",
            x=0.7, y=1.2,
            xanchor="left", yanchor="top",
            showactive=True
        )
    ]
)

# ▼ 軸フォーマット調整(必要な場合アンコメント)
# Y軸:小数1桁
# fig.update_yaxes(tickformat=".1f")
# Y軸:単位つき
# fig.update_yaxes(tickformat=",d", ticksuffix=" 秒")

# ▼ 軸範囲(必要に応じて有効化)
# fig.update_xaxes(range=[0, 100])
# fig.update_yaxes(range=[0, 500])

fig.show()

本記事の内容が役に立った、または「いいね!」と思っていただけましたら、ぜひ100円の応援サポートをよろしくお願いします!
頂いたサポートは、次の便利ツールの開発や情報共有の励みになります。
応援やフィードバックをいただけると、とても嬉しいです!

ここから先は

0字

¥ 100

Amazon Payで支払うと最大2%還元のチャンス! 9/30まで

よろしければ応援お願いします! いただいたチップはクリエイターとしての活動費に使わせていただきます!