毎日Markdownで日記やメモ(ジャーナル)を書いてる人、結構いると思います。
でも、それってただ書きっぱなしになってませんか?
せっかく書き溜めたテキストデータなので、**「自分って無意識にどういう口癖を使ってるんだろう?」「どういう思考の癖があるんだろう?」**と気になって、Pythonでサクッと計量テキスト分析をやってみました。
今回は Janome を使って、自分のテキストデータを「コーパス」として扱い、傾向を抽出してみたスクリプトを共有します。
モチベーションと前提
-
やりたいこと: 自分の日記ディレクトリ (
.mdファイル群) を形態素解析して、無意識の癖を定量化したい。 -
環境: Python 3.x,
janome(pip install janome)
1. まずは基本:よく使う単語とフレーズ(N-gram)を出す
とりあえず、文章をバラして頻出単語と2-gram(よく連続する2単語)を出してみます。
Markdown特有のノイズ(URLやコードブロック、記号など)が混じると結果がゴミだらけになるので、泥臭く正規表現で削り落とすのが地味に一番重要です。
import os
import re
from collections import Counter
from janome.tokenizer import Tokenizer
def clean_text(text):
# Markdownの辛み(記号やURL)を消し飛ばす
text = re.sub(r'#.*|\[(.*?)\]\(.*?\)|http\S+', '', text)
text = re.sub(r'```.*?```|<!--.*?-->', '', text, flags=re.DOTALL)
text = re.sub(r'[\*\_\~\-\+\[\]]', '', text)
# 日本語が含まれる行だけ残す
return '\n'.join([l for l in text.split('\n') if re.search(r'[あ-んア-ン一-龥]', l)])
def basic_analysis(directory):
t = Tokenizer()
word_freq = Counter()
bigrams = Counter()
files = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.md')]
for filepath in files:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
text = clean_text(f.read())
sentences = re.split(r'[。!?\n]', text)
for sentence in sentences:
sentence = sentence.strip()
if not sentence: continue
tokens = list(t.tokenize(sentence))
words = []
for token in tokens:
pos = token.part_of_speech.split(',')[0]
# 「の」「こと」みたいなノイズ(ストップワード)を弾く
stop_words = {'こと', 'の', 'よう', 'それ', 'これ', 'する', 'いる', 'ある', 'なる'}
if pos in ['名詞', '動詞', '形容詞'] and token.base_form not in stop_words:
if re.search(r'[あ-んア-ン一-龥]', token.base_form):
word_freq[token.base_form] += 1
if re.search(r'[あ-んア-ン一-龥]', token.surface):
words.append(token.surface)
# 2-gramを抽出
for i in range(len(words)-1):
bigrams[words[i] + ' ' + words[i+1]] += 1
print("--- 頻出単語 ---")
for w, c in word_freq.most_common(10):
print(f"{w}: {c}")
print("\n--- 頻出フレーズ (2-gram) ---")
for b, c in bigrams.most_common(10):
print(f"{b}: {c}")
# 実行例
# basic_analysis('./journals')
これだけでも結構面白いです。
たとえば、「やる」「作る」が多い人は行動ベースで考えているし、「〜し たい」「〜ない と」が上位に来る人は、タスクや現状に対する焦りがそのままテキストに出ていることがわかります。
2. もう一歩踏み込む:語彙の多様性・共起ネットワーク・TF-IDF近似
単なるカウントだけだと飽きるので、もう少し分析っぽくしてみます。
以下を計算して出力します。
- TTR (Type-Token Ratio): 語彙の豊富さ。色んな言葉を使っているか?
- 共起語: 同じ文脈(一文)の中で、どの単語とどの単語がよく一緒に使われるか?
- TF-IDF近似: 単なる「私」や「する」ではなく、**「自分のログ全体の中で際立って特徴的な言葉」**は何か?
import math
from collections import Counter
def advanced_analysis(directory):
t = Tokenizer()
total_tokens = 0
unique_tokens = set()
sentence_lengths = []
co_occurrence = Counter()
doc_word_counts = []
doc_freq = Counter()
files = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.md')]
N = len(files)
for filepath in files:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
text = clean_text(f.read())
sentences = re.split(r'[。!?\n]', text)
doc_words = Counter()
for sentence in sentences:
sentence = sentence.strip()
if not sentence: continue
tokens = list(t.tokenize(sentence))
sentence_lengths.append(len(tokens))
words_in_sentence = set()
for token in tokens:
total_tokens += 1
unique_tokens.add(token.base_form)
if token.part_of_speech.split(',')[0] in ['名詞', '動詞', '形容詞']:
if re.search(r'[あ-んア-ン一-龥]', token.base_form):
words_in_sentence.add(token.base_form)
doc_words[token.base_form] += 1
# 共起語の抽出
words_list = sorted(list(words_in_sentence))
for i in range(len(words_list)):
for j in range(i+1, len(words_list)):
co_occurrence[(words_list[i], words_list[j])] += 1
doc_word_counts.append(doc_words)
for w in doc_words.keys():
doc_freq[w] += 1
ttr = len(unique_tokens) / total_tokens if total_tokens > 0 else 0
avg_len = sum(sentence_lengths) / len(sentence_lengths) if sentence_lengths else 0
print(f"TTR (語彙の豊富さ): {ttr:.4f}")
print(f"平均一文長: {avg_len:.2f} 形態素\n")
print("--- 強く結びつく単語ペア ---")
for (w1, w2), c in co_occurrence.most_common(5):
print(f"{w1} × {w2}: {c}回")
# TF-IDF近似の計算
tf_idf_scores = {}
total_tf = Counter()
for dw in doc_word_counts:
for w, count in dw.items():
total_tf[w] += count
for w, tf in total_tf.items():
if tf > 5:
idf = math.log10(N / (doc_freq[w] + 1))
tf_idf_scores[w] = tf * idf
print("\n--- 特徴語 (TF-IDF近似) ---")
sorted_tfidf = sorted(tf_idf_scores.items(), key=lambda x: x[1], reverse=True)
for w, score in sorted_tfidf[:5]:
print(f"{w}: {score:.2f}")
# advanced_analysis('./journals')
やってみると、TTRが高い日は「色んな概念について深く思考を広げている」ことが分かったり、一文の長さから「論理的にこねくり回してるのか、感情を箇条書きで吐き出してるだけなのか」が見えたりします。
TF-IDFを見ると、「最近『記事』とか『お金』についてばっかり考えてるな自分…」みたいな偏りに気づけて良いです。
まとめ
とにかく前処理(不要文字の削除)がすべてです。生データをそのまま突っ込むとマークダウンのゴミ [x] などがランキングを埋め尽くして絶望します。
自分のローカルに眠っているメモや日記がある方は、ぜひ一度流し込んでみてください。意外な自分の癖に気づけるかもしれません。