テキスト分析ツール
テキストを読み込んで単語頻度・文字数・センテンス数を分析し頻度グラフとワードクラウドで可視化するツール。
1. アプリ概要
長い文章でどの言葉を繰り返し使っているかは、読み返すだけでは数えきれません。このツールはテキストを貼り付けるかファイルから読み込み、単語頻度・文字数・センテンス数を分析して、頻度グラフとワードクラウドで可視化します。
wordcloud(任意)を入れていないときは、ワードクラウド タブに pip install wordcloud の案内が出るだけで、基本統計・単語頻度・頻度グラフ・文字解析はこれまでどおり使えます(19〜23行・318〜337行)。
日本語は形態素解析をしないため、記号・句読点・空白で区切られたひと続きの文字列が1語として数えられます(252行の re.findall)。
2. 機能一覧
- 「📂 ファイルを開く」ボタン(
_open_file())・「▶ 分析」ボタン(_analyze())・「🗑 クリア」ボタン(_clear())で操作 - イベント
<<Modified>>から_on_text_change()を実行 ttk.Treeviewによる表形式の一覧表示filedialog.askopenfilename()によるファイル選択ダイアログmessagebox.showwarning()によるダイアログ通知- ウィンドウはタイトル「テキスト分析ツール」・サイズ 1050x660 で起動
3. 事前準備・環境
Python 3.11 以上(Ubuntu で 3.14・3.15 の起動を確認) / Windows 11(実機)で起動・基本操作を確認 / Linux Mint 22.3(仮想マシン)で起動・画面表示を確認(macOS は未検証)
Windows 11(実機)で起動と基本操作を確認しています(全機能の網羅テストではありません)。Linux Mint 22.3(仮想マシン)では起動と画面表示を確認しました。macOS は必要なライブラリ(pip install)を入れれば動作する想定ですが、未検証です。
- Python 3.11 以上(Ubuntu で 3.14・3.15 の起動を確認)
- OS: Windows 11(実機で起動・基本操作を確認)・Linux Mint 22.3(仮想マシンで起動・画面表示を確認)
インストールが必要なライブラリ
pip install matplotlib wordcloud
matplotlib は頻度グラフ、wordcloud はワードクラウド タブに使います。どちらも try〜except ImportError で読み込むので(9〜23行)、入っていなくてもアプリは起動し、該当タブに案内が出るだけです。wordcloud を入れない場合でも、単語頻度の表と頻度グラフ(matplotlib)はこれまでどおり使えます。
ワードクラウドの描画は Linux Mint 22.3(仮想マシン・Python 3.12・matplotlib 3.11・wordcloud 1.9.6)で日本語を含めて表示されることを確認しました(和文フォントは Noto Sans CJK JP を自動検出)。Windows での実描画は当サイトではまだ確認していません。Windows では Meiryo などを先に探す作りですが(26〜53行)、実際に描けたかどうかを確認するまでは断定しません。
Python 3.15 で使う場合の注意: 2026年10月14日時点で、wordcloud 1.9.6 には Python 3.15 用の Windows(64bit)向けビルド済みファイル(wheel)が PyPI にありません(3.14 までは提供されています。PyPI の配布ファイル一覧で確認)。そのため Windows で 3.15 を使うと、pip install wordcloud はソースからのビルドになり、C コンパイラーが必要になる可能性があります(当サイトでは 3.15 でのインストールは未確認です)。matplotlib 3.11.2 には 3.15 用の wheel があります(2026年10月12日時点)。ワードクラウド タブを使うなら、wheel で入れられる 3.14 系で動かすほうが手間がかかりません。Windows に 3.14 も入っているなら py -3.14 -m pip install matplotlib wordcloud で 3.14 に入れて py -3.14 app080.py で起動するか、3.15 用 wheel の公開を待ってください。
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import re
import os
import math
import threading
import collections
try:
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import font_manager
MATPLOTLIB_AVAILABLE = True
except ImportError:
MATPLOTLIB_AVAILABLE = False
try:
from wordcloud import WordCloud
WORDCLOUD_AVAILABLE = True
except ImportError:
WORDCLOUD_AVAILABLE = False
# 和文フォントの候補(グラフとワードクラウドで共通・上から順に探す)
JP_FONT_NAMES = ("Meiryo", "Yu Gothic", "Noto Sans CJK JP", "Noto Sans JP",
"Hiragino Sans", "IPAexGothic", "MS Gothic", "TakaoPGothic")
if MATPLOTLIB_AVAILABLE:
# グラフ内の日本語が豆腐(□)にならないよう、入っている和文フォントを1つ選ぶ
_installed = {f.name for f in font_manager.fontManager.ttflist}
for _name in JP_FONT_NAMES + ("DejaVu Sans",):
if _name in _installed:
matplotlib.rcParams["font.family"] = _name
break
matplotlib.rcParams["axes.unicode_minus"] = False
def find_jp_font_path():
"""ワードクラウド用に和文フォントのファイルパスを探す(無ければ None)"""
if MATPLOTLIB_AVAILABLE:
for name in JP_FONT_NAMES:
for f in font_manager.fontManager.ttflist:
if f.name == name:
return f.fname
for path in ("C:/Windows/Fonts/meiryo.ttc", "C:/Windows/Fonts/YuGothM.ttc",
"C:/Windows/Fonts/msgothic.ttc",
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf",
"/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc"):
if os.path.exists(path):
return path
return None
JP_FONT_PATH = find_jp_font_path()
class App080:
"""テキスト分析ツール"""
# 日本語・英語ストップワード(簡易版)
STOP_WORDS = {
"の", "に", "は", "を", "た", "が", "で", "て", "と", "し", "れ", "さ",
"ある", "いる", "も", "する", "から", "な", "こと", "として", "い", "や",
"れる", "など", "なっ", "ない", "この", "ため", "その", "あっ", "よう",
"また", "もの", "という", "あり", "まで", "られ", "なる", "へ", "か",
"だ", "これ", "によって", "により", "おり", "より", "による", "ず", "なり",
"a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
"have", "has", "had", "do", "does", "did", "will", "would", "could",
"should", "may", "might", "shall", "can", "to", "of", "in", "for",
"on", "with", "at", "by", "from", "as", "or", "and", "but", "not",
"this", "that", "it", "its", "i", "you", "he", "she", "we", "they",
}
def __init__(self, root):
self.root = root
self.root.title("テキスト分析ツール")
self.root.geometry("1050x660")
self.root.minsize(1050, 660)
self.root.configure(bg="#1e1e1e")
self._text = ""
self._build_ui()
def _build_ui(self):
header = tk.Frame(self.root, bg="#252526", pady=6)
header.pack(fill=tk.X)
tk.Label(header, text="📝 テキスト分析ツール",
font=("Noto Sans JP", 12, "bold"),
bg="#252526", fg="#4fc3f7").pack(side=tk.LEFT, padx=12)
# ツールバー
tb = tk.Frame(self.root, bg="#2d2d2d", pady=4)
tb.pack(fill=tk.X)
ttk.Button(tb, text="📂 ファイルを開く",
command=self._open_file).pack(side=tk.LEFT, padx=4)
tk.Button(tb, text="▶ 分析", command=self._analyze,
bg="#1565c0", fg="white", relief=tk.FLAT,
font=("Arial", 10, "bold"), padx=12, pady=4,
activebackground="#0d47a1", bd=0).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="🗑 クリア",
command=self._clear).pack(side=tk.LEFT, padx=4)
self.stop_var = tk.BooleanVar(value=True)
tk.Checkbutton(tb, text="ストップワード除外",
variable=self.stop_var,
bg="#2d2d2d", fg="#ccc", selectcolor="#3c3c3c",
activebackground="#2d2d2d").pack(side=tk.LEFT, padx=8)
# メインエリア (PanedWindow)
paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
paned.pack(fill=tk.BOTH, expand=True, padx=4, pady=4)
# 左: テキスト入力
left = tk.Frame(paned, bg="#1e1e1e")
paned.add(left, weight=1)
tk.Label(left, text="テキスト入力", bg="#1e1e1e", fg="#888",
font=("Arial", 9)).pack(anchor="w")
self.text_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
font=("Arial", 10), relief=tk.FLAT,
insertbackground="white",
wrap=tk.WORD)
txsb = ttk.Scrollbar(left, command=self.text_input.yview)
self.text_input.configure(yscrollcommand=txsb.set)
txsb.pack(side=tk.RIGHT, fill=tk.Y)
self.text_input.pack(fill=tk.BOTH, expand=True)
self.text_input.bind("<<Modified>>", self._on_text_change)
# 右: 分析結果
right = tk.Frame(paned, bg="#1e1e1e")
paned.add(right, weight=1)
# 統計ノートブック
nb = ttk.Notebook(right)
nb.pack(fill=tk.BOTH, expand=True)
# タブ1: 基本統計
tab_stat = tk.Frame(nb, bg="#1e1e1e")
nb.add(tab_stat, text="基本統計")
self.stat_text = tk.Text(tab_stat, bg="#0d1117", fg="#c9d1d9",
font=("Courier New", 9), relief=tk.FLAT,
state=tk.DISABLED)
self.stat_text.pack(fill=tk.BOTH, expand=True)
# タブ2: 単語頻度
tab_freq = tk.Frame(nb, bg="#1e1e1e")
nb.add(tab_freq, text="単語頻度")
cols = ("rank", "word", "count", "pct")
self.freq_tree = ttk.Treeview(tab_freq, columns=cols,
show="headings", height=18)
self.freq_tree.heading("rank", text="#")
self.freq_tree.heading("word", text="単語")
self.freq_tree.heading("count", text="出現数")
self.freq_tree.heading("pct", text="割合")
self.freq_tree.column("rank", width=40, anchor="center")
self.freq_tree.column("word", width=140, anchor="w")
self.freq_tree.column("count", width=70, anchor="center")
self.freq_tree.column("pct", width=70, anchor="center")
fsb = ttk.Scrollbar(tab_freq, command=self.freq_tree.yview)
self.freq_tree.configure(yscrollcommand=fsb.set)
fsb.pack(side=tk.RIGHT, fill=tk.Y)
self.freq_tree.pack(fill=tk.BOTH, expand=True)
# タブ3: グラフ
tab_chart = tk.Frame(nb, bg="#0d1117")
nb.add(tab_chart, text="グラフ")
if MATPLOTLIB_AVAILABLE:
self.fig = Figure(figsize=(5, 4), facecolor="#0d1117")
self.ax = self.fig.add_subplot(111)
self.mpl_canvas = FigureCanvasTkAgg(self.fig, master=tab_chart)
self.mpl_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
else:
tk.Label(tab_chart,
text="pip install matplotlib でグラフが表示されます",
bg="#0d1117", fg="#555").pack(expand=True)
# タブ4: ワードクラウド(wordcloud が入っていれば matplotlib の Figure に描く・無ければ案内だけ)
tab_wc = tk.Frame(nb, bg="#0d1117")
nb.add(tab_wc, text="ワードクラウド")
self.wc_note_var = tk.StringVar()
if WORDCLOUD_AVAILABLE and MATPLOTLIB_AVAILABLE:
self.wc_fig = Figure(figsize=(5, 4), facecolor="#0d1117")
self.wc_ax = self.wc_fig.add_subplot(111)
self.wc_ax.axis("off")
self.wc_canvas = FigureCanvasTkAgg(self.wc_fig, master=tab_wc)
self.wc_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
self.wc_note_var.set("「▶ 分析」を押すとワードクラウドを表示します")
else:
self.wc_note_var.set("pip install wordcloud でワードクラウドが表示されます")
tk.Label(tab_wc, textvariable=self.wc_note_var, bg="#0d1117", fg="#888",
font=("Arial", 9)).pack(side=tk.BOTTOM, fill=tk.X)
# タブ5: 文字解析
tab_char = tk.Frame(nb, bg="#1e1e1e")
nb.add(tab_char, text="文字解析")
self.char_text = tk.Text(tab_char, bg="#0d1117", fg="#c9d1d9",
font=("Courier New", 9), relief=tk.FLAT,
state=tk.DISABLED)
self.char_text.pack(fill=tk.BOTH, expand=True)
self.status_var = tk.StringVar(value="テキストを入力または読み込んでください")
tk.Label(self.root, textvariable=self.status_var,
bg="#252526", fg="#858585", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)
# ── ファイル ─────────────────────────────────────────────
def _open_file(self):
path = filedialog.askopenfilename(
filetypes=[("テキスト", "*.txt *.md *.csv"), ("すべて", "*.*")])
if not path:
return
for enc in ("utf-8-sig", "utf-8", "shift-jis", "cp932", "euc-jp"):
try:
with open(path, encoding=enc) as f:
text = f.read()
break
except UnicodeDecodeError:
continue
self.text_input.delete("1.0", tk.END)
self.text_input.insert("1.0", text)
self.status_var.set(f"読み込み: {path}")
def _on_text_change(self, _=None):
self.text_input.edit_modified(False)
def _clear(self):
self.text_input.delete("1.0", tk.END)
for widget in (self.stat_text, self.char_text):
widget.configure(state=tk.NORMAL)
widget.delete("1.0", tk.END)
widget.configure(state=tk.DISABLED)
self.freq_tree.delete(*self.freq_tree.get_children())
# ── 分析 ─────────────────────────────────────────────────
def _analyze(self):
text = self.text_input.get("1.0", tk.END).strip()
if not text:
messagebox.showwarning("警告", "テキストを入力してください")
return
self._text = text
self.status_var.set("分析中...")
threading.Thread(target=self._do_analyze, daemon=True).start()
def _do_analyze(self):
text = self._text
use_stop = self.stop_var.get()
# --- 基本統計 ---
chars = len(text)
chars_ns = len(text.replace(" ", "").replace("\n", ""))
lines = text.count("\n") + 1
words_raw = re.findall(r"\w+", text, re.UNICODE)
words = len(words_raw)
sents = len(re.findall(r"[。.!?!?]", text)) or 1
paras = len([p for p in text.split("\n\n") if p.strip()])
avg_wl = sum(len(w) for w in words_raw) / max(1, words)
avg_sw = words / sents
# ユニーク語数
unique = len(set(w.lower() for w in words_raw))
# 日本語文字率
jp_chars = len(re.findall(r"[\u3040-\u9fff]", text))
jp_rate = jp_chars / max(1, chars) * 100
stat_str = (
f"━━ 基本統計 ━━━━━━━━━━━━━━━━━━\n"
f" 文字数 (全体): {chars:>8,}\n"
f" 文字数 (空白除): {chars_ns:>8,}\n"
f" 行数: {lines:>8,}\n"
f" 段落数: {paras:>8,}\n"
f" 単語数: {words:>8,}\n"
f" ユニーク語数: {unique:>8,}\n"
f" 文数 (句点等): {sents:>8,}\n"
f" 平均語長: {avg_wl:>8.2f} 文字\n"
f" 平均文長: {avg_sw:>8.1f} 語/文\n"
f" 日本語文字率: {jp_rate:>7.1f}%\n"
f"\n━━ 可読性 ━━━━━━━━━━━━━━━━━━━\n"
)
# Flesch-Kincaid (英語向け簡易計算)
syllables = sum(max(1, len(re.findall(r"[aeiouAEIOU]", w)))
for w in words_raw)
fk = max(0, 206.835 - 1.015 * (words / sents)
- 84.6 * (syllables / max(1, words)))
stat_str += f" Flesch Reading Ease: {fk:.1f}\n"
# --- 単語頻度 ---
tokens = [w.lower() for w in words_raw]
if use_stop:
tokens = [t for t in tokens if t not in self.STOP_WORDS and len(t) > 1]
counter = collections.Counter(tokens)
top50 = counter.most_common(50)
total_t = sum(counter.values())
# --- 文字解析 ---
hiragana = len(re.findall(r"[\u3041-\u3096]", text))
katakana = len(re.findall(r"[\u30a1-\u30f6]", text))
kanji = len(re.findall(r"[\u4e00-\u9fff]", text))
ascii_a = len(re.findall(r"[a-zA-Z]", text))
digits = len(re.findall(r"\d", text))
punct = len(re.findall(r"[、。,.「」『』【】・…—\-!?!?]", text))
char_str = (
f"━━ 文字種別 ━━━━━━━━━━━━━━━━━\n"
f" ひらがな: {hiragana:>7,}\n"
f" カタカナ: {katakana:>7,}\n"
f" 漢字: {kanji:>7,}\n"
f" ASCII英字:{ascii_a:>7,}\n"
f" 数字: {digits:>7,}\n"
f" 句読点等: {punct:>7,}\n"
)
# --- ワードクラウド(画像はこのスレッドで作り、Figure への描画は _update_ui で行う)---
wc_img, wc_note = self._make_wordcloud(top50)
self.root.after(0, self._update_ui, stat_str, top50, total_t, char_str,
wc_img, wc_note)
@staticmethod
def _make_wordcloud(top50):
"""単語頻度からワードクラウド画像(RGB の配列)を作る。戻り値は (画像 or None, 画面に出す注記)"""
if not (WORDCLOUD_AVAILABLE and MATPLOTLIB_AVAILABLE):
return None, "pip install wordcloud でワードクラウドが表示されます"
freqs = dict(top50)
note = ""
if JP_FONT_PATH is None: # 和文フォントが無いときは英字の単語だけ描く
freqs = {w: c for w, c in freqs.items() if w.isascii()}
note = "和文フォントが見つからないため英字の単語だけを描画しました"
if not freqs:
return None, "和文フォントが見つからず、英字の単語も無いため描画できません"
if not freqs:
return None, "描画する単語がありません"
try:
wc = WordCloud(width=520, height=400, font_path=JP_FONT_PATH,
background_color="#0d1117", colormap="cool")
return wc.generate_from_frequencies(freqs).to_array(), note
except Exception as e:
return None, f"ワードクラウドを描画できません: {e}"
def _update_ui(self, stat_str, top50, total_t, char_str, wc_img, wc_note):
# 基本統計
self.stat_text.configure(state=tk.NORMAL)
self.stat_text.delete("1.0", tk.END)
self.stat_text.insert("1.0", stat_str)
self.stat_text.configure(state=tk.DISABLED)
# 単語頻度
self.freq_tree.delete(*self.freq_tree.get_children())
for i, (word, cnt) in enumerate(top50, 1):
pct = cnt / max(1, total_t) * 100
self.freq_tree.insert("", tk.END,
values=(i, word, cnt, f"{pct:.2f}%"))
# グラフ
if MATPLOTLIB_AVAILABLE and top50:
words = [w for w, _ in top50[:20]]
counts = [c for _, c in top50[:20]]
self.ax.clear()
self.ax.set_facecolor("#0d1117")
bars = self.ax.barh(list(reversed(words)), list(reversed(counts)),
color="#4fc3f7")
self.ax.set_title("単語頻度 Top 20", color="#c9d1d9", fontsize=10)
self.ax.tick_params(colors="#8b949e", labelsize=7)
for spine in self.ax.spines.values():
spine.set_edgecolor("#30363d")
self.ax.grid(True, axis="x", color="#21262d", linewidth=0.5)
self.fig.tight_layout()
self.mpl_canvas.draw()
# ワードクラウド
if wc_img is not None:
self.wc_ax.clear()
self.wc_ax.imshow(wc_img, interpolation="bilinear")
self.wc_ax.axis("off")
self.wc_fig.tight_layout()
self.wc_canvas.draw()
self.wc_note_var.set(wc_note)
# 文字解析
self.char_text.configure(state=tk.NORMAL)
self.char_text.delete("1.0", tk.END)
self.char_text.insert("1.0", char_str)
self.char_text.configure(state=tk.DISABLED)
self.status_var.set(
f"分析完了: {len(self._text):,} 文字 Top語: "
f"{top50[0][0] if top50 else '-'} ({top50[0][1] if top50 else 0} 回)")
if __name__ == "__main__":
root = tk.Tk()
app = App080(root)
root.mainloop()
5. コード解説
テキスト分析ツールのコードを、実際に書かれている実装に沿って解説します。
クラス設計とコンストラクタ
App080 クラスにアプリの全機能をまとめています(メソッド9個・全392行)。__init__ ではタイトル「テキスト分析ツール」とウィンドウサイズ 1050x660 を設定します。あわせて状態を保持する変数(self._text)を初期化し、最後に _build_ui() で画面を組み立てます。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
ウィジェット構成
画面は tk.Frame×9・tk.Label×5・ttk.Button×2・tk.Button・tk.Checkbutton・ttk.PanedWindow・tk.Text×3・ttk.Scrollbar×2・ttk.Notebook・ttk.Treeview で構成しています。PanedWindow を使っているので、ペインの境界をマウスでドラッグして幅を変えられます。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
イベント処理とボタンの接続
「📂 ファイルを開く」ボタン(_open_file())・「▶ 分析」ボタン(_analyze())・「🗑 クリア」ボタン(_clear())を command= で接続しています。また bind("<<Modified>>", ...) から _on_text_change() を呼べるようにイベントも登録しています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
例外処理
try-except で Exception・ImportError・UnicodeDecodeError を捕捉しています。あわせて messagebox.showwarning() のダイアログで、ユーザーへの通知・確認を行います。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1ファイルを作成する
新しいファイルを作成して app080.py と保存します。外部ライブラリ
matplotlib・wordcloudを使います。先にpip install matplotlib wordcloudを実行してください。 -
2クラスの骨格を作る
App080クラスを定義し、__init__と、末尾のroot = tk.Tk()~mainloop()の最小構成を書いて、まず空のウィンドウが出ることを確認します。 -
3ウィンドウを設定する
title("テキスト分析ツール")・geometry("1050x660")・configure(bg="#1e1e1e")・minsize(1050, 660)をコンストラクタで設定します。 -
4画面部品を並べる
_build_ui()の中で、tk.Frame×9・tk.Label×5・ttk.Button×2・tk.Button・tk.Checkbutton・ttk.PanedWindow・tk.Text×3・ttk.Scrollbar×2・ttk.Notebook・ttk.Treeviewを作って配置します(掲載コードと同じ並び順で書くとレイアウトが一致します)。まず表示だけ確認しましょう。 -
5イベントを接続する
「2. 機能一覧」で挙げた各ボタンを
command=で対応するメソッド(_open_file()・_analyze()・_clear())につなぎます。bind("<<Modified>>", ...)の登録も忘れずに。 -
6中心になるメソッドを実装する
アプリの本体である
_update_ui()(48行)・_open_file()(15行)・_analyze()(8行)など を実装します。 -
7動作確認する
python app080.pyで起動し、各ボタンが反応することを確認します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。
💡 ダークモードを追加する
bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。
💡 クラス定数を書き換えて動きを変える
データはクラス定数(STOP_WORDS)にまとまっています。中身を書き換えて動きが変わることを確かめましょう。複数の定数が対応関係にある場合は、セットで整合させる必要がある点に注意してください。
💡 設定ダイアログ
フォントサイズや色などの設定をユーザーが変更できるオプションダイアログを追加しましょう。
8. よくある問題と解決法
❌ 文字のフォントが崩れる・見た目が違う
原因:このコードは font=("Noto Sans JP", ...) のようにフォント名を直接指定しています。環境にこのフォントが入っていないと、OS が代わりのフォントで表示するため、見本と見た目が変わることがあります。
解決法:書体の指定は、見出しが Noto Sans JP、入力欄・「▶ 分析」ボタン・ステータスバーが Arial、「基本統計」「文字解析」タブが Courier New という分担です。Courier New の代わりに文字幅が不ぞろいな書体が入ると、{chars:>8,} のように空白で右寄せした数値の桁位置が上下で食い違うことがあります。グラフとワードクラウドは別の仕組みで、JP_FONT_NAMES の候補から見つけた和文フォントを matplotlib と find_jp_font_path() が使い、見つからなければワードクラウドは英字の単語だけを描きます。桁をそろえるには、stat_text と char_text の font 引数を、OS にある別の等幅書体名に書き換えます。
❌ ウィンドウの大きさが画面に合わない
原因:geometry("1050x660") の固定サイズで起動するためです。
解決法:geometry() と minsize() の両方の数値を書き換えて起動サイズを調整してください。なお、このコードにはウィンドウサイズを固定する設定(resizable の指定)が無いため、ウィンドウの端をドラッグしたサイズ変更は既定どおり可能です。ただし minsize(1050, 660) を指定しているため、起動時の大きさより小さくは縮められません(縮めると画面部品が表示されなくなるのを防ぐためです)。
9. 練習問題
アプリの理解を深めるための練習問題です。気になるものから挑戦してみてください(課題3は定型の発展課題です)。このアプリはネットワークにつなぐライブラリを読み込んでいません(1-23行)。分析は手元で完結しますが、ほかの人の個人情報が入った文書を練習に使うのは避けてください。練習には自分で書いた文章か、使ってよいと分かっている文章を選んでください。他人の文章から作ったワードクラウドの画像を公開したり配ったりするときは、元の文章の権利者の許可が必要になることがあります。下の課題1・課題2は、左のテキスト入力欄に短い文を打ち込んで「▶ 分析」を押すだけで確かめられます。wordcloud を入れていなくても取り組めます(ワードクラウド タブに案内が出るだけで、単語頻度の表と頻度グラフは使えます)。
日本語の「単語」は re.findall(r"\w+") で切り出しているため(252行)、分かち書きされず句読点までの塊が1語になります(ワードクラウド タブを足す前からの仕様です)。単語単位にしたい人は、形態素解析のライブラリ(janome など)に置き換えるところまで発展させてみてください。どのくらい結果が変わるかは、実際に試して確かめてください。
-
課題1:小数点を文の区切りに数えない
「文数 (句点等)」は
[。.!?!?]に当てはまる文字の個数をそのまま数えています(254行)。半角ピリオドも1件と数えるので、小数を含む文章では文の数が実際より増えます。期待結果:
円周率は 3.14 です。と入れて「▶ 分析」を押す。いまは基本統計タブの「文数 (句点等)」が 2 と出るが、直したあとは 1 になる。同じ画面の「平均文長」も2.0 語/文から4.0 語/文に変わる。合格条件
- 数字に挟まれた半角ピリオドを区切りに数えない(
在庫は 1,000 個です。単価は 12.5 円。は 3 から 2 になる) - 英文の文末のピリオドはこれまでどおり数える(
I have a pen. It is a pen!は 2 のまま) - 文字数・行数・段落数・単語数・ユニーク語数・日本語文字率の値は変わらない
ヒント①(どこを触るか): 触るのは1か所です。
_do_analyze()のsents = len(re.findall(r"[。.!?!?]", text)) or 1(254行)を書き換えます。
ヒント②(使うもの): 半角ピリオドだけ条件を付ける書き方があります。re.findall(r"[。!?!?]|(?<!\d)\.(?!\d)", text)のようにすると、直前と直後が数字でないピリオドだけを拾えます。(?<!\d)は「直前が数字でない」、(?!\d)は「直後が数字でない」という意味です。
つまずきやすい点:sentsは表示だけの値ではありません。平均文長(257行)と Flesch Reading Ease(282-284行)の分母にもなっているので、文数が変わると2つとも動きます。末尾のor 1を外すと、区切り記号が1つも無い文章で 0 による割り算になります。 - 数字に挟まれた半角ピリオドを区切りに数えない(
-
課題2:単語頻度の「割合」を全語数を分母にして出す
単語頻度タブの「割合」は、ストップワードを除いたあとの語数を分母にしています(289行・292行・349行)。除外で消える語が多いほど、残った語の割合が大きく出ます。
期待結果:
I have a pen. It is a pen!と入れて、「ストップワード除外」にチェックを入れたまま「▶ 分析」を押す。いまは pen が「出現数 2・割合 100.00%」と出るが、直したあとは 25.00%(全8語のうち2回)になる。チェックを外したときは、これまでと同じく a と pen が 25.00% で並ぶ。合格条件
- ストップワード除外がONのとき、割合の分母が除外前の全語数になる
- ストップワード除外がOFFのときの割合は、これまでと同じ値が出る
- #・単語・出現数の3列と、グラフタブの「単語頻度 Top 20」・ワードクラウド タブはこれまでどおり
ヒント①(どこを触るか): 触るのは3か所です。
_do_analyze()の最後にある_update_uiの呼び出し(315-316行)は、いま引数を6つ渡しています。その末尾に全語数を足します。受け取る_update_ui()の定義(339行)にも同じ位置へ引数を1つ足します。あとはpct = cnt / max(1, total_t) * 100(349行)の分母を差し替えるだけです。
ヒント②(使うもの): 全語数はwords = len(words_raw)(252-253行)として計算済みなので、そのまま渡せます。列見出しの文言を変えたい場合はself.freq_tree.heading("pct", ...)(154行)も直せます。ただし今回の合格条件には含めません。
つまずきやすい点: 引数を足す位置は、呼び出し側と定義側で同じにしてください(wc_img・wc_noteの間に割り込ませると、ワードクラウドの画像と注記が入れ替わります)。total_tはcounter.values()の合計です(292行)。ストップワード除外がONのときは1文字の語も落ちるので(289行)、分母を全語数にすると、表に並ぶ割合を全部足しても 100% にはなりません。100% で合わせたいなら、分母は変えずに見出しを「除外後の割合」と書き換える方向もあります。 -
課題3:保存機能の追加
処理結果をファイルに保存する機能を追加しましょう。
写経中に赤いエラー文が出たら、Pythonエラー一覧&解決法(英語メッセージ逆引き)で原因と直し方をすぐ確認できます。
このレベルのアプリが作れたら、入門書の次の「作るための本」へ進む時期です。実践におすすめのPython本(当サイトの参考書ランキング総合3〜4位)で自動化とコードの書き方の2冊を、その直後の用途別専門書の節でデータ分析・Web・AIの本を比較しています。