見出し画像

「生成AIを活用したデータサイエンス入門」をChatGPTで寄り道写経~Vol.10「衆院選候補者の政策回答をChatGPTで分析」

書籍の監修者 竹村彰通 先生


この記事は、書籍「生成AIを活用したデータサイエンス入門:実例で学ぶ教科書」第9章「政治は右と左で語れるのか」を ChatGPT と一緒に進めたときの「データ分析ドタバタ劇」をドキュメンタリー風に書いたものです。

書籍を開いて生成AIと一緒にデータ分析の旅に出かけましょう🚀


はじめに


書籍「生成AIを活用したデータサイエンス入門」のご紹介

書籍「生成AIを活用したデータサイエンス入門:実例で学ぶ教科書」(学術図書出版社、以下「書籍」または「テキスト」と呼びます)をご紹介いたします。

テキストは2026年3月に発売され、その名の通り、ChatGPTを全面活用するデータ分析の入門書 です。

第9章 政治は右と左で語れるのか


今回の取組み

シナリオの概要
テキスト第9章の「アンケートデータを要約してグルーピングする分析」を ChatGPT と一緒に進めます。

2024年衆議院議員選挙の候補者に対するアンケート調査データをお借りして、次の分析を実施します。

  • 主成分分析でデータを2軸に要約(次元削減)

  • クラスター分析で似ている政党をグループ分け(セグメンテーション)

🤖

前回のダラダラの反省から、今回は書籍のプロンプトに準拠して分析を実施します。
書籍どおりの会話が成立しているかどうか、お楽しみに🍀
そして今回は、アディショナルタイムで分析を深堀りします。お楽しみに🍀

利用した生成AI
ChatGPT(GPT-5.5 インテリジェンス:標準)

テキストで学べるテーマ
主成分分析、k-means クラスタリング

🤖🤖🤖

データの取得

東京大学の谷口研究室と朝日新聞社の共同調査によって得られたデータをお借りします。

■ WEBサイト

WEBサイトが開かれたら、2024年衆院選の「政治家調査」からコードブックとデータをダウンロードします。

https://www.masaki.j.u-tokyo.ac.jp/utas/utasindex#2024  より引用(部分)

🤖🤖🤖

テキストに沿ったデータ分析

テキストのプロンプトをお借りして、データの読み込みと分析を行います。

1️⃣ データの読み込みと前処理

データファイル「2024UTASP20241125.csv」をアップロードしてプロンプトを投げます🤾

引用プロンプト:
このCSVファイルの中身をPythonで読み込んで表示してください。ただし、日本語はCP932で入力されています。また、政治家の個人名(NAMEとKANA)は表示しないでください。

🤖 ChatGPTの回答


CP932でCSVを読み込み、NAME と KANA を除外して表示します。

思考時間: 数秒

CP932でCSVを読み込み、NAME と KANA を除外して表示しました。
確認結果です。

  • 元データ:1344行 × 122列

  • 表示データ:1344行 × 120列

  • 非表示にした列:NAME, KANA


利用するデータを絞り込みします。

引用プロンプト:
このデータからQ4(Q4_1~Q4_19)の回答を取り出して表示し、回答者数を報告してください。

🤖 ChatGPTの回答


Q4系列だけに絞って、個人名列は使わずに確認します。回答者数は「Q4_1~Q4_19のいずれかに回答がある行数」として数えます。

思考時間: 2 ~ 3 秒

Q4(Q4_1~Q4_19)の回答を取り出して表示しました。

  • 抽出した列数:19列

  • 回答者数:1344人

ここでの回答者数は、Q4_1~Q4_19 のいずれかに回答が入っている行数として数えています。


続いて、データのうち「無回答」を除外します。
コードブックファイル「2024UTASP_codebook20241125.docx」をアップロードします。
コードブックに無回答を探し出すヒントが書かれています。

引用プロンプト:
このWORDファイルに、各項目の質問文や入力規則が記載されています。Q4の回答データから無回答を含むケースを削除し、残ったケース数と割合を報告してください。以降、この完全データを分析対象とします。

🤖 ChatGPTの回答


コードブックではQ4は Q4_1~Q4_19 で、各項目の 99 が無回答として定義されています。ここでは欠損値と 99 のどちらも無回答として除外します。

思考時間: 18s

Q4の完全データを作成しました。

コードブックでは、Q4は Q4_1~Q4_19 で構成され、各項目の選択肢は 1~5、99 が無回答として定義されています。

以降の分析対象は、Q4_1~Q4_19 のすべてに 1~5 の有効回答が入っている 1176ケース とします。

完全データは以下に保存しました。

Q4完全データCSVをダウンロード


1176 名の候補者による 19 個の質問の回答を分析します。
1176 名とは、データの標本サイズが 1176 であることを意味します。
19 個の回答とは、データが 19 次元であることを意味しています。
つまり、分析に用いるデータの形状は $${1176 \times 19}$$ です。

最初に主成分分析でデータを 19 次元から 2 次元に圧縮して、解釈してみましょう。

🤖🤖🤖

2️⃣ 主成分分析

主成分分析は、多数の量的変数を少数の新しい変数(主成分)へと圧縮(次元削減)する、教師なし学習手法の一種です。
1から5のアンケート回答は本来、計算ができない「質的変数(順序尺度)」ですが、分析の現場では量的変数とみなして扱うことも少なくありません。今回はこのアプローチを採用し、量的変数として主成分分析を適用します

書籍のプロンプトを少々改造します。
本来のプロンプトはスクリープロットの描画を依頼するものです。
スクリープロットが初出なので説明を追加します。

改造プロンプト:
このデータを主成分分析で次元削減します。相関行列の固有値を計算し、スクリープロットを作成して表示してください。あわせて、主成分分析におけるスクリープロットとは何か、今回のスクリープロットから得られる結果を説明してください。

🤖 ChatGPTの回答


Q4完全データを使い、相関行列にもとづくPCAとして、標準化済み変数の相関行列の固有値を計算します。

思考時間: 4s


最初の描画で環境フォントの都合により日本語ラベルが文字化けしました。数値計算自体は完了していますが、表示用のスクリープロットだけ英字ラベルで再作成します。

相関行列の固有値を計算し、スクリープロットを作成しました。

スクリープロット画像をダウンロード

🤖

分析対象は、前回作成したQ4完全データの 1176ケース × 19項目 です。

スクリープロットとは何か

主成分分析におけるスクリープロットとは、横軸に主成分番号、縦軸に固有値を取った折れ線グラフです。
固有値は、その主成分が元データのばらつきをどれだけ説明しているかを表します。相関行列を使った主成分分析では、元の19項目それぞれの分散を1として扱うため、固有値の合計は項目数と同じ 19 になります。
スクリープロットでは、固有値が急に下がったあと、なだらかになる位置を見ます。この「折れ曲がり」の前までを、主要な主成分候補として考えます。

今回のスクリープロットから得られる結果

今回のデータでは、第1主成分の固有値が8.1656と非常に大きく、第1主成分だけで全体の42.98%を説明しています。これは、Q4の19項目にかなり強い共通方向があることを示しています。

第2主成分の固有値は2.6253で、第1主成分よりかなり小さくなります。ただし、第2主成分までで累積寄与率は 56.79% になります。したがって、2次元に圧縮して可視化する場合でも、半分以上の情報は保持できます。

固有値が1以上の主成分は、PC1〜PC4の 4個 です。いわゆるカイザー基準で見るなら、主成分数は4が候補になります。この場合、累積寄与率は 69.02% です。

一方、スクリープロットの形を見ると、PC1からPC2で大きく低下し、PC2からPC4まではまだ少し情報を持ち、PC5以降はかなりなだらかです。したがって、今回の実務的な見方は次のようになります。

可視化目的なら2主成分、情報保持を少し重視するなら4主成分が候補です。
以降の分析対象としては、まず PC1・PC2の2次元プロット を作ると、政治家回答の大まかな配置を確認しやすいです。


スクリープロットは主成分分析による次元削減後の主成分数(次元数)を決める際に参考にできるものです。
例えば、エルボー法(肘法)によれば、スクリープロットの折れ線が「肘」のように折れ曲がっている数、あるいはその1つ前の数を選択します。
今回のスクリープロットでは、緩やか=肘 になるのは 3 なので、主成分数は 3 または 2 ということになります。

書籍にならって主成分を 2 にして主成分分析を実行します。

引用プロンプト:
主成分の数は2にしたいと思います。Q4の完全データを標準化し、主成分分析を実行してください。第2主成分までの寄与率と累積寄与率を報告してください。

🤖 ChatGPTの回答


主成分数を2に固定して実行します。前回の完全データ1176ケースを標準化し、PC1 と PC2 の得点も保存します。

思考時間: 2 ~ 3 秒

Q4完全データを標準化し、主成分数を 2 に指定して主成分分析を実行しました。

分析対象は 1176ケース × 19項目 です。

第1主成分だけで全体のばらつきの 42.98% を説明しています。第2主成分まで使うと、累積寄与率は 56.79% になります。

つまり、Q4の19項目を2次元に圧縮すると、元データに含まれる情報の約 56.8% を保持することになります。

作成したデータはこちらです。

主成分得点CSVをダウンロード
標準化済みQ4完全データCSVをダウンロード


第1主成分と第2主成分の累積寄与率は 56.8% です。
この2つの主成分でデータ全体の 56.8% の情報を保持しています。
ということは、逆にデータ全体の 43.2% を無視することになります。

続いて、各質問の側面から主成分の結果を眺めます。
具体的には、各質問の「主成分負荷量」を見ます。
主成分はデータの要約と言われていますが、その内容は明示されません。
そこで、主成分負荷量から各主成分の意味を検討します。

🤖

引用プロンプト:
求めた主成分負荷量の一覧表を作成してください。表の左端に、項目番号と質問内容を追加してください。質問内容が長い場合は適宜要約してください。

🤖 ChatGPTの回答


Q4の質問文をコードブックに対応させ、PC1・PC2の主成分負荷量を一覧化します。ここでは、主成分負荷量を「標準化済み各項目と主成分得点の相関」として計算します。

思考時間: 6s

Q4の主成分負荷量一覧表を作成しました。
質問内容は、コードブックに記載されたQ4_1~Q4_19の質問文をもとに、表で読みやすい長さに要約しています。

Q4主成分負荷量一覧表CSVをダウンロード

🤖

今回の主成分分析では、寄与率は以下のとおりです。

注意点として、主成分の符号は数学的には反転しても同じ意味を持ちます。したがって、PC1の正負そのものよりも、「どの項目が同じ向きにまとまり、どの項目が反対向きにまとまっているか」を読むのが大切です。


書籍の一覧表と違っていますね。

まず形式的な点では、書籍はヒートマップで可視化していますが、こちらのChatGPTは表形式です。書籍の方が見やすくてよいですね!

もっと重要な点は、書籍の数値とこちらのChatGPTの数値が違っていることです。困ったなぁ…

書籍の値は相関行列の「固有ベクトル」そのものです。
一方でこちらのChatGPTの値は「固有ベクトルに固有値の平方根を乗算」したものです。
一般論ではこちらのChatGPTの計算結果を主成分負荷量と呼ぶことが多いでしょう

ということで、このChatGPTの計算結果を使って分析を継続します!

🤖

表に戻って、第1主成分負荷量(PC1負荷量)と第2主成分負荷量(PC2負荷量)から、第1主成分と第2主成分の意味を検討しましょう。
値の大きさと正負の符号の向きで似ている質問をよく見てみます。

第1主成分のプラスの大きな項目は「防衛力強化」「北朝鮮への圧力」「首相靖国参拝」「治安のための権利制約」です。
マイナスの大きな項目は「同性婚を認める」「選択的夫婦別姓を認める」「クオータ制導入」です。

書籍は「保守 - リベラル」と名付けます。
プラス側が保守、マイナス側がリベラルです。

第2主成分のプラスの大きな項目は「法人税率引き上げ」「公共事業で雇用確保」「景気対策の財政出動」です。
マイナスの大きな項目は「雇用の流動化」「政策金利引き上げ」「農協組織改革」です。

書籍は財政面を強調して「積極 - 緊縮財政」と名付けます。
プラス側が積極財政、マイナス側が緊縮です。

ということで、ChatGPTに名付けた結果を共有します。

引用プロンプト:
第1主成分を「保守-リベラル」、第2主成分を「積極-緊縮財政」とよぶことにします。

🤖 ChatGPTの回答


了解です。

以降、この主成分分析では次の名称で扱います。

この命名は、主成分負荷量の傾向とも整合しています。

PC1は、防衛力強化、靖国参拝、権利制約、公務員抑制などが正方向に出ており、選択的夫婦別姓、同性婚、クオータ制などが負方向に出ているため、保守-リベラル軸として自然に読めます。

PC2は、公共事業、財政出動、法人税引き上げなどが正方向に出ており、雇用流動化、政策金利引き上げ、農協改革などが負方向に出ているため、積極-緊縮財政軸として扱えます。


各質問に関する主成分負荷量を用いて、第1主成分・第2主成分が何者かを解釈しました。

🤖

続いて、各候補者の側面から主成分の結果を眺めます。
具体的には、各候補者の「主成分得点」を見ます。
各候補者が、第1主成分「保守かリベラル化」、第2主成分「積極か緊縮か」のどの立場なのかを見ることができます。

引用プロンプト:
候補者ごとに二つの主成分を計算し、散布図を作成して表示してください。マーカーは候補者の公認政党で色分けし、丸印(●)で塗りつぶしてください。色が重複しないよう、公認政党数に応じたカラーを用意してください。凡例では政党名を略称としてください。

🤖 ChatGPTの回答


Thought for 42s

候補者ごとに第1主成分・第2主成分を計算し、公認政党ごとに色分けした散布図を作成して表示しました。

  • 分析対象ケース数:1176

  • 公認政党数:12

  • マーカー:塗りつぶしの丸印(●)

  • 凡例:政党略称

散布図画像をダウンロード

凡例の略称は次の対応です。

この散布図では、横軸が 第1主成分「保守-リベラル」、縦軸が 第2主成分「積極-緊縮財政」 です。
右側ほどリベラル、左側ほど保守、上側ほど積極財政、下側ほど緊縮財政として読むことができます。


んー、英語表記は読みにくいので…

追加プロンプト:
この散布図は日本語表記をしてください。
あわせてPythonコードを提示してください。

🤖 ChatGPTの回答


Thought for 30s

日本語表記の散布図を作成しました。

日本語版散布図画像をダウンロード

以下が、今回の散布図を再現するPythonコードです。

コードは掲載を省略します。

今回の実行環境では Noto Sans CJK JP を使って日本語表示しました。ローカル環境で文字化けする場合は、候補フォント名を手元の環境にある日本語フォントへ置き換えると表示しやすくなります。


散布図にすると各候補者の位置づけが分かりやすいですね!
横軸を読むと、0 より右側が「保守」、0 より左側が「リベラル」です。
縦軸を読むと、0 より上側が「積極財政」、0 より下側が「緊縮財政」です。

点の色で政党を識別できますので、各政党の2軸のカラーがこのチャートから見えるのではないでしょうか?

たとえば、第1主成分(横軸)の「保守 ‐ リベラル」では各政党が 0 をほぼ跨ぐことなく、保守とリベラルに分かれている感じが読み取れます。
一方で、第2主成分(縦軸)の「積極 - 緊縮財政」では 0 を跨ぐ政党が多く見られます。政党内で各候補者の考えがばらついているようです。

🤖🤖🤖

3️⃣ クラスター分析:k-means 法

ここからは次の分析手法に進みます。
各候補者の主成分得点から所属政党別の平均と標準偏差を算出します。
つまり、政党別に、第1主成分の「平均」と「標準偏差」、第2主成分の「平均」と「標準偏差」の4つの変数が生み出されます。

この政党別4変数データ用いて、似ている政党をグループ分けするクラスター分析を行います。
今回は k-means 法というクラスター分析手法を使います。

🤖

引用プロンプト:
政党ごとに主成分の平均値と標準偏差を計算してください。ただし、諸派と無所属の候補者については集計から除外してください。これを以降の分析の特徴量とします。政党番号の順で一覧表にしてください。

🤖 ChatGPTの回答


諸派と無所属を除外し、政党番号1〜10だけで PC1・PC2 の平均値と標準偏差を集計します。政党番号順の特徴量表として保存します。

思考時間: 2 ~ 3 秒

政党ごとに、第1主成分・第2主成分の 平均値標準偏差 を計算しました。
諸派と無所属は除外し、政党番号順に並べています。

政党別主成分特徴量CSVをダウンロード

🤖

確認結果です。

  • Q4完全データ:1176ケース

  • 諸派・無所属を除外後:1114ケース

  • 集計対象政党数:10政党

以降の分析では、この政党別特徴量を分析対象として扱います。


ChatGPTが暴れ始めました…
政党別の表の数値が全然違います(書籍の結果参照)。

実は最初に表示された「政党別 主成分特徴量(諸派・無所属除外)」の表には正しい値が出ています。

回答末尾の「</>」で提示された Python コードも正しい値を計算できています。

ひとまず、指摘しないで先に進めましょう。

🤖

k-means 法を用いて、似ている政党のクラスター(グループ分け)を見つけてみます。
書籍にならって、クラスター(グループ)の数を3つに設定します。

引用プロンプト:
これら四つの特徴量を用いて、k-means法で政党を分類してください。
クラスター数は3とします。
クラスターごとに政党の一覧を表示してください。

🤖 ChatGPTの回答


政党別特徴量の4列(PC1平均・PC1標準偏差・PC2平均・PC2標準偏差)を標準化し、クラスター数3のk-means法で分類します。乱数シードを固定して再現できる形にします。

思考時間: 4s

k-means法で、10政党を 3クラスター に分類しました。
使用した特徴量は以下の4つです。

  • PC1平均

  • PC1標準偏差

  • PC2平均

  • PC2標準偏差

k-means法では距離にもとづいて分類するため、4つの特徴量は標準化してから分類しました。

クラスターごとの政党一覧

政党番号順の分類結果

分類結果はこちらです。

政党別k-means分類結果CSVをダウンロード
クラスター別政党一覧CSVをダウンロード


書籍のクラスターと違っていますね。
自民と立民が同じクラスターなんて、変です…

可視化しておかしなところを探しましょう。

引用プロンプト:
特徴量のうち、各政党の平均値を丸印(●)で散布図にプロットしてください。また、標準偏差をプロットから伸びた矢印で表し、クラスターで色分けしてください。政党の略称をラベルにして散布図に追加してください。

🤖 ChatGPTの回答


政党ごとの平均値を丸印で、標準偏差を矢印で表した散布図を作成しました。
色分けはクラスターごと、ラベルは政党略称です。

散布図画像をダウンロード

この図では、

  • 点の位置

    • 横軸:PC1平均(第1主成分平均)

    • 縦軸:PC2平均(第2主成分平均)

  • 矢印の長さ

    • 右向き矢印:PC1標準偏差

    • 上向き矢印:PC2標準偏差

を表しています。

したがって、各政党が主成分空間のどこに位置しているかに加えて、政党内でのばらつきの大きさも同時に確認できます。


おかしなチャートが現れました…困ったなぁ…

ぱっと見で変な点は、1つの点から矢印が2本伸びているところ。
第1主成分と第2主成分という2次元でのバラツキの方向を見たいので、矢印は1本にしたいです。

それよりも気になるのは、形成されたクラスターが変なところ。
近い点がクラスターを形成するはずなのに、たとえば、クラスター3では社民・共産と公明が離れています。間に位置する立民のほうが社民・公明と近いです。

クラスター形成ミスの原因は、実は1つ前の回答で見つかりました。

政党別特徴量の4列(PC1平均・PC1標準偏差・PC2平均・PC2標準偏差)を標準化し、クラスター数3のk-means法で分類します。

チャップリン(ChatGPTの愛称)は、4つの特徴量を標準化してしまったようです。
この4つの特徴量は候補者別の主成分得点の平均と標準偏差です。
先に行った主成分分析によって、スケールは統一されています。
したがって、標準化をする必要はありません。

正していきましょう。

追加プロンプト:
かなり残念な結果になっています。
まず、クラスター2と3が未分化の状態です。おそらくクラスタリングにあたって特徴量を標準化してしまったことが原因(問題)では無いでしょうか?
つぎに標準偏差は平均から1本の矢印が伸びる形式にしてほしいです。いまの x ,y が別の矢印で表現される形式は見たことがないし、駄目です。
クラスター分析をやり直して、クラスターごとの政党の一覧と散布図の描画を提示し直してください。

🤖 ChatGPTの回答


ご指摘のとおりです。特徴量を標準化すると、平均値による政党間距離の意味が弱まり、標準偏差も別々の矢印にすると図としてかなり不自然でした。未標準化の4特徴量でクラスタリングをやり直し、標準偏差は1本のベクトル矢印で描き直します。

思考時間: 10s

ご指摘のとおり、前回の処理はよくありませんでした。
今回は 4特徴量を標準化せず に、k-means法をやり直しました。また、標準偏差は平均点から伸びる 1本の矢印 として描き直しました。

クラスターごとの政党一覧

政党番号順の分類結果

散布図

修正版散布図をダウンロード

今回の図では、

  • 丸印(●):各政党の平均値

    • 横軸:PC1平均「保守-リベラル」

    • 縦軸:PC2平均「積極-緊縮財政」

  • 矢印:標準偏差ベクトル

    • 始点:その政党の平均値

    • 横方向成分:PC1標準偏差

    • 縦方向成分:PC2標準偏差

として描いています。

作成したCSVはこちらです。

未標準化k-means分類結果CSVをダウンロード
未標準化k-meansクラスター別政党一覧CSVをダウンロード


うまくいったようです。
クラスターごとの政党の一覧も、散布図も、書籍の結果と合っています。

3つにグループ分けした政党が似ているかどうか、みなさんの感覚値と合っていますか?

k-means 法によるクラスター分析の最後に、最初に決めたクラスター数「3」が適切かどうかチェックしましょう(書籍通りです)。

引用プロンプト:
クラスター数が適切かチェックしたいのでシルエット分析を実行して結果を表示してください。クラスター数は2から6まで変化させてください。

🤖 ChatGPTの回答


クラスター数2〜6について、前回と同じく未標準化の4特徴量を使ってシルエット分析を行います。平均シルエット係数の一覧と、確認用の折れ線グラフを表示します。

思考時間: 5s

シルエット分析を実行しました。
対象は前回と同じく、未標準化の4特徴量です。

  • PC1平均

  • PC1標準偏差

  • PC2平均

  • PC2標準偏差

シルエット分析グラフをダウンロード

結果

読み取り

平均シルエット係数が最も高いのは、クラスター数2 です。

ただし、クラスター数3も 0.436 で、クラスター数2の 0.455 と大きな差ではありません。
したがって、純粋に分離のよさだけを見るなら 2クラスター がやや優勢です。

一方で、今回の分析目的が「政党配置を大きく3類型に分けて解釈すること」であれば、3クラスターも十分に採用可能です。
ただし、シルエット分析だけを見ると、3クラスターが明確に最適とは言い切れません。

作成したCSVはこちらです。

シルエット分析サマリーCSVをダウンロード
政党別シルエット係数詳細CSVをダウンロード


シルエット分析では、シルエット係数の大きいクラスター数を良しとします。
シルエット係数はクラスターの固まり方を評価する指標です。
クラスターが混じり合うことなく、適切に分かれているかを示します。
具体的には、同じクラスターのデータ点の近いかどうか(凝集度)と、異なるクラスターが離れているか(分離度)を評価しています。

平均シルエット係数を見ると、クラスター2 > 4 > 3 の順で適切と読み取れます。

今回のクラスター数 3 はChatGPTが補足するとおり、「今回の分析目的が「政党配置を大きく3類型に分けて解釈すること」であれば、3クラスターも十分に採用可能」なのでしょう。

🤖

ここまで書籍のプロンプトに準拠して進めました。
書籍を引用させていただき、ありがとうございました。
分析結果のまとめに入りましょう。

🤖🤖🤖

4️⃣ まとめ

追加プロンプト:
分析結果を報告書にまとめてください。 少なくとも、分析サマリー、分析目的、利用した分析手法の概要、分析手法ごとの結果、分析手法の結果から言えることおよび得られた傾向・知見を含めてください。

🤖 ChatGPTの回答


報告書として読める形に整理します。前提として、今回の政党配置では図と政党別平均値に合わせ、PC1は左側を保守、右側をリベラルとして記述します。

思考時間: 19s

コードブックでは、Q4は Q4_1~Q4_19 の19項目で、各項目の 99 が無回答として定義されています。今回の報告書は、この定義にもとづいて無回答を除外した完全データを対象にまとめています。

🤖

Q4回答データにもとづく政党配置の主成分分析・クラスター分析報告書

1. 分析サマリー

本分析では、2024年衆院選候補者調査のQ4回答データを用いて、候補者および政党の政策的な位置づけを可視化・分類した。

Q4は19項目からなる政策意見項目であり、各候補者の回答をもとに主成分分析を行った。無回答を含むケースを除外した結果、分析対象となるQ4完全データは1176ケースであった。

主成分分析では、第1主成分を「保守-リベラル」、第2主成分を「積極-緊縮財政」と解釈した。第1主成分の寄与率は42.98%、第2主成分の寄与率は13.82%であり、第2主成分までの累積寄与率は56.79%であった。したがって、Q4の19項目の情報のうち、約56.8%を2次元で表現できた。

その後、諸派と無所属を除いた10政党について、政党ごとに第1主成分・第2主成分の平均値と標準偏差を計算した。この4つの特徴量を用いてk-means法によるクラスター分析を行った。最終的には、特徴量を標準化しない条件で、クラスター数3の分類を採用した。

分類結果は、次の3類型であった。

シルエット分析では、クラスター数2の平均シルエット係数が最も高かった。ただし、クラスター数3との差は大きくなく、政治的解釈のしやすさを考えると、3クラスター分類にも一定の妥当性がある。

全体として、Q4回答にもとづく政党配置では、保守-リベラル軸が最も大きな分散を説明しており、政党間の違いを把握するうえで中心的な軸であることが確認された。また、第2主成分によって、財政政策や経済政策に関する違いも一定程度表現された。

🤖

2. 分析目的

本分析の目的は、候補者のQ4回答データを用いて、政党ごとの政策的な位置づけを定量的に整理することである。

具体的には、次の3点を目的とした。

第一に、Q4の19項目をそのまま扱うのではなく、主成分分析によって少数の軸に要約することで、候補者や政党の位置関係を視覚的に把握しやすくすることである。

第二に、政党ごとの主成分得点の平均値と標準偏差を求め、政党の中心的な位置と政党内のばらつきを特徴量として整理することである。

第三に、その政党別特徴量を用いてクラスター分析を行い、政党をいくつかの政策的類型に分類することである。

🤖

3. 使用データと前処理

分析には、2024年衆院選候補者調査のQ4回答データを用いた。Q4は、政策や価値観に関する19項目の設問で構成されている。

各項目の回答は、おおむね次の5件法である。

前処理では、Q4_1からQ4_19までのいずれかに無回答を含むケースを除外した。ここでの無回答には、欠損値と、コードブック上の無回答コードである99の両方を含めた。

処理の結果は次のとおりである。

以降の候補者レベルの主成分分析では、この1176ケースを分析対象とした。

政党別の特徴量を作成する段階では、諸派と無所属を除外した。その結果、政党別集計の対象は1114ケース、10政党となった。

🤖

4. 分析手法の概要

4.1 主成分分析

主成分分析は、多数の変数を、少数の合成変数に要約する手法である。今回の分析では、Q4の19項目をもとに、候補者の政策的な位置を2つの軸に要約した。

今回の主成分分析では、各Q4項目を標準化したうえで分析した。これは、すべての項目を同じ尺度で比較するためである。

主成分数は2とした。第1主成分を「保守-リベラル」、第2主成分を「積極-緊縮財政」と呼ぶことにした。

なお、主成分分析では、主成分の符号は数学的には反転しても同じ意味を持つ。したがって、本報告では、実際の政党配置を読みやすくするため、散布図上の位置関係に合わせて、第1主成分は左側を保守、右側をリベラルとして解釈する。

4.2 政党別特徴量の作成

候補者ごとに第1主成分得点と第2主成分得点を計算した後、政党ごとに以下の4つの特徴量を作成した。

平均値は政党の中心的な政策位置を表し、標準偏差は政党内の候補者のばらつきを表す。

4.3 k-means法

k-means法は、データ点を指定した数のクラスターに分ける非階層的クラスタリング手法である。各データ点は、クラスター中心との距離にもとづいて分類される。

今回の分析では、政党を単位として、上記4特徴量を用いてk-means法を実行した。クラスター数は3とした。

当初は4特徴量を標準化してクラスタリングを行ったが、平均値による政党間の位置関係が弱まり、クラスターの解釈が不明瞭になった。そのため、最終的には、4特徴量を標準化せずにk-means法を実行した。

今回の特徴量は、いずれも主成分得点から派生したものであり、平均値と標準偏差が同じ主成分空間上の量として解釈できる。そのため、未標準化で距離を計算した方が、今回の図示・解釈には適していると判断した。

4.4 シルエット分析

シルエット分析は、クラスタリング結果のまとまりのよさを評価する方法である。

シルエット係数は、各データ点について、同じクラスター内の近さと、他クラスターとの遠さを比較して計算される。値はおおむね -1 から 1 の範囲を取り、1に近いほどよく分類されていることを示す。

今回は、クラスター数を2から6まで変化させ、それぞれの平均シルエット係数を計算した。

🤖

5. 分析結果

5.1 主成分分析の結果

Q4完全データ1176ケースに対して、標準化後に主成分分析を行った。

第2主成分までの寄与率は次のとおりである。

第1主成分だけで、Q4回答のばらつきの42.98%を説明した。これは、Q4項目全体に共通する大きな政策対立軸が存在することを示している。

第2主成分まで含めると、累積寄与率は56.79%となった。Q4の19項目を2次元に圧縮しても、全体の情報の半分以上を保持できている。

スクリープロットでは、第1主成分の固有値が大きく、第2主成分以降で急に小さくなる形が確認された。したがって、第1主成分がもっとも支配的な軸であり、第2主成分はそれを補助する軸として解釈できる。

5.2 主成分の解釈

第1主成分は「保守-リベラル」軸と解釈した。

この軸では、参政党、日本保守党、維新、自民党などが保守側に位置し、共産党、社民党、立憲民主党、れいわ新選組などがリベラル側に位置した。したがって、Q4回答にもとづく政党配置では、保守-リベラルの対立軸がもっとも大きな構造として表れている。

第2主成分は「積極-緊縮財政」軸と解釈した。

この軸では、維新、公明、立憲民主党などが比較的上側に位置し、参政党、日本保守党、れいわ新選組、共産党などが下側に位置した。ただし、第2主成分は第1主成分ほど明瞭な分離軸ではなく、財政政策だけでなく、経済政策、行政改革、雇用政策などを含む複合的な軸として読む必要がある。

5.3 政党別特徴量の結果

諸派と無所属を除外し、10政党について第1主成分・第2主成分の平均値と標準偏差を計算した。

PC1平均を見ると、保守側には日本保守党、参政党、維新、自民党が位置している。一方、リベラル側には共産党、社民党、立憲民主党、れいわ新選組が位置している。

PC2平均を見ると、上側には維新、公明、立憲民主党、国民民主党が位置している。一方、下側には参政党、日本保守党、れいわ新選組、共産党、社民党が位置している。

政党内のばらつきについては、れいわ新選組、国民民主党、自民党、立憲民主党でPC1標準偏差が大きい。これは、これらの政党では、保守-リベラル軸上で候補者間の幅が比較的大きいことを示している。

一方、共産党はPC1標準偏差が小さく、PC2標準偏差も小さい。これは、候補者間の回答傾向が比較的一貫していることを示している。

5.4 k-means法による政党分類の結果

未標準化の4特徴量を用いて、クラスター数3でk-means法を実行した。

分類結果は次のとおりである。

政党番号順の分類結果は次のとおりである。

クラスター1は、参政党と日本保守党で構成される。両党はPC1平均が強く保守側にあり、PC2平均も下側に位置している。したがって、保守色が強く、他の保守系政党とは別のまとまりを形成している。

クラスター2は、自民党、維新、公明、国民民主党で構成される。このクラスターは、保守側から中間付近にかけて位置する政党群である。ただし、政党ごとの位置には幅があり、維新は保守寄りかつPC2が高く、公明は中間寄りかつPC2が高い。自民党と国民民主党は比較的中間的な位置にある。

クラスター3は、立憲民主党、共産党、れいわ新選組、社民党で構成される。このクラスターは、PC1平均がリベラル側に位置する政党群である。ただし、PC2方向では、立憲民主党は上側、れいわ新選組は下側に位置しており、同じクラスター内にも一定の違いがある。

5.5 シルエット分析の結果

クラスター数を2から6まで変化させ、平均シルエット係数を計算した。

平均シルエット係数がもっとも高いのは、クラスター数2であった。したがって、分離のよさだけを見るなら、2クラスターが最も自然である。

ただし、クラスター数3の平均シルエット係数は0.436であり、クラスター数2の0.455と大きな差はない。また、クラスター数4も0.442であり、数値上は3クラスターとほぼ同程度である。

この結果から、今回のデータでは、クラスター数2から4の間に明確な優劣はなく、分析目的に応じてクラスター数を選ぶ余地があると考えられる。

政党を大きく「保守系」と「リベラル系」に分けるだけであれば、2クラスターが適している。一方で、「強い保守系」「中間・実務保守系」「リベラル系」という3類型で解釈するなら、3クラスターにも十分な意味がある。

🤖

6. 分析結果から言えること

6.1 保守-リベラル軸がもっとも重要な分離軸である

第1主成分の寄与率は42.98%であり、Q4回答データのばらつきの大きな部分を説明している。このことから、候補者の政策意見を整理するうえで、保守-リベラル軸がもっとも中心的な分離軸であると言える。

政党別の平均値を見ても、日本保守党、参政党、維新、自民党が保守側に位置し、共産党、社民党、立憲民主党、れいわ新選組がリベラル側に位置している。この並びは、一般的な政治的イメージともおおむね整合している。

6.2 第2主成分は財政・経済政策を含む補助的な軸である

第2主成分の寄与率は13.82%であり、第1主成分ほど大きくはない。ただし、第2主成分によって、政党間の追加的な違いを読み取ることができる。

特に、維新や公明は第2主成分の上側に位置し、参政党、日本保守党、れいわ新選組は下側に位置している。この軸は、単純な左右対立では説明しきれない、財政政策、経済政策、行政改革、雇用政策などの違いを反映している可能性がある。

ただし、第2主成分については「積極-緊縮財政」と命名しているものの、完全に財政政策だけを表す軸ではない。複数の政策項目が組み合わさった複合的な軸として解釈する必要がある。

6.3 政党内のばらつきにも違いがある

政党ごとの標準偏差を見ると、候補者間のばらつきには違いがある。

たとえば、れいわ新選組、国民民主党、自民党、立憲民主党では、PC1標準偏差が比較的大きい。これは、同じ政党内でも保守-リベラル軸上の回答傾向に幅があることを示している。

一方、共産党はPC1標準偏差・PC2標準偏差ともに小さい。これは、候補者間の回答が比較的一貫していることを示している。

政党を平均値だけで見ると、政党内の多様性を見落とす可能性がある。今回、標準偏差を特徴量に含めたことで、政党の中心位置だけでなく、政党内のまとまりの強さも分析に反映できた。

6.4 3クラスター分類は政治的解釈として有用である

シルエット分析では、クラスター数2がやや優勢であった。しかし、3クラスター分類は、政治的な解釈として有用である。

3クラスター分類では、参政党・日本保守党が独立した保守系クラスターとして抽出された。自民党、維新、公明、国民民主党は、より中間的・実務的な保守寄り政党群としてまとまった。立憲民主党、共産党、れいわ新選組、社民党は、リベラル系政党群としてまとまった。

この分類は、単純な2分法よりも、保守側内部の違いを表現できる点に意味がある。

🤖

7. 得られた傾向・知見

今回の分析から、主に次の知見が得られた。

第一に、Q4回答データには、保守-リベラル軸が明確に表れている。この軸は、政党間の違いを説明するもっとも重要な軸である。

第二に、財政・経済政策に関連する第2主成分は、保守-リベラル軸では説明しきれない政党間の違いを補足している。ただし、この軸は単純な財政政策軸ではなく、複合的な政策姿勢を表している可能性がある。

第三に、政党の位置は平均値だけでなく、標準偏差を見ることでより豊かに理解できる。候補者間のばらつきが大きい政党と小さい政党があり、政党内の一貫性には差がある。

第四に、政党分類としては、2クラスターが統計的にはやや優勢である一方、3クラスターは政治的解釈に優れている。特に、参政党と日本保守党を他の保守系政党から分けて捉えられる点で、3クラスター分類は有用である。

第五に、特徴量の標準化は常に望ましいとは限らない。今回のように、平均値と標準偏差が同じ主成分空間上で解釈され、平均位置そのものが重要な意味を持つ場合には、未標準化の方が分析目的に合うことがある。

🤖

8. 留意点

今回の分析には、いくつかの留意点がある。

第一に、主成分分析は線形な次元削減手法であるため、Q4回答のすべての構造を完全に表現しているわけではない。第2主成分までの累積寄与率は56.79%であり、残りの情報は第3主成分以降に残っている。

第二に、政党別クラスター分析の対象は10政党である。データ点が少ないため、シルエット分析の結果は参考指標として扱うべきである。クラスター数の選択は、数値指標だけでなく、政治的な解釈可能性も含めて判断する必要がある。

第三に、主成分の符号は任意である。そのため、保守-リベラル軸の左右の向きは、結果を読むために定めた便宜的な向きである。重要なのは、どの政党が同じ方向にまとまり、どの政党が反対方向に位置しているかである。

第四に、Q4は政策意見項目であり、政党の全体像を完全に代表するものではない。政党のイデオロギーや政策的位置をより総合的に把握するには、他の設問群や外部情報もあわせて分析する必要がある。

🤖

9. 結論

本分析では、Q4回答データを用いて、候補者および政党の政策的位置を主成分分析とクラスター分析によって整理した。

主成分分析の結果、Q4回答の最大の構造は「保守-リベラル」軸であり、第2主成分として「積極-緊縮財政」と解釈できる補助的な軸が得られた。第2主成分までの累積寄与率は56.79%であり、19項目の情報を2次元に要約することには一定の妥当性があった。

政党別特徴量を用いたk-means法では、3クラスター分類により、参政党・日本保守党、自民党・維新・公明・国民民主党、立憲民主党・共産党・れいわ新選組・社民党という3つのまとまりが得られた。

シルエット分析では2クラスターがやや優勢であったが、3クラスター分類は、保守側内部の違いを表現できる点で有用である。したがって、今回の分析では、統計的な分離のよさと政治的な解釈可能性の両方を踏まえ、3クラスター分類を採用する意義があると考えられる。

以上より、Q4回答データは、政党間の政策的な位置関係を可視化し、政党の類型を把握するうえで有効な情報を提供している。

🤖

以上が報告書です。
数値結果は、これまでの分析で作成した完全データ、主成分分析、未標準化k-means、シルエット分析の結果にもとづいて整理しています。


「6. 分析結果から言えること」、「7. 得られた傾向・知見」、「8. 留意点」にまとめられたことが個人的に参考になりました。
たとえば…

  • 主成分分析の第1主成分は一般的な政党イメージと合致しています。

  • 各候補者の主成分得点の標準偏差から、政党によって、候補者の考えがばらつく状況が見られました。

  • クラスター数 3 のクラスター分析では、一般的な政党イメージに近いクラスターが得られました。

    • 保守系クラスター

    • より中間的・実務的な保守寄りクラスター

    • リベラル系クラスター

さて、書籍と同じ分析を一通り体感したところで…

恒例のアディショナルタイムに突入です!
地図のない分析の航海に出発です🚀

🤖🤖🤖

寄り道データ分析


追加分析手法の検討

今回の Q4 データを他の手法で分析しましょう。
分析手法を検討します。

プロンプト:
ありがとう。ここまでは書籍のプロンプトに準拠して分析を進めました。ここからは追加的な分析の可能性を探ります。今回のQ4データに対して、主成分分析とクラスター分析だけでなく、さらに加えて分析したいことを列挙してください。

🤖 ChatGPTの回答


もちろんです。ここから追加分析を考えるなら、「政党配置を深掘りする分析」「Q4項目そのものを理解する分析」「候補者属性と結びつける分析」「分析手法の妥当性を検証する分析」の4方向が有力です。

1. Q4項目の構造を詳しく見る分析

1.1 相関行列・相関ヒートマップ

Q4_1〜Q4_19の項目間相関を確認します。
これは、たとえば次のような問いに答える分析です。

「防衛力強化に賛成する候補者は、靖国参拝にも賛成しやすいのか」
「同性婚に賛成する候補者は、選択的夫婦別姓にも賛成しやすいのか」
「経済・財政系の項目はまとまった回答傾向を持っているのか」

主成分分析の前提確認にもなります。

1.2 項目クラスター分析

候補者ではなく、Q4項目を分類します。
たとえば、19項目が次のようなグループに分かれるかを見ます。

主成分分析で得られた軸が、どの項目群に支えられているかを確認できます。

1.3 因子分析

主成分分析は「データのばらつきを要約する手法」ですが、因子分析は「背後にある潜在因子を推定する手法」です。
今回なら、次のような潜在因子が想定できます。

主成分分析よりも、政策態度の「構造」を説明しやすくなる可能性があります。

🤖

2. 候補者・政党の位置を深掘りする分析

2.1 政党別の分布可視化

政党ごとのPC1・PC2得点について、平均だけでなく分布を見ます。
候補としては次の図があります。

これはかなり有用です。今回、標準偏差を特徴量に入れましたが、標準偏差だけでは分布の形までは分かりません。

2.2 政党内の多様性ランキング

政党ごとに、PC1・PC2の標準偏差、または2次元上の分散を使って、政党内のばらつきランキングを作ります。
たとえば、次のような問いに答えられます。

「最も候補者の政策位置がまとまっている政党はどこか」
「最も党内に幅がある政党はどこか」
「保守-リベラル軸で幅がある党と、財政・経済軸で幅がある党は違うのか」

これは、政党組織の一体性や候補者の多様性を見る分析として面白いです。

2.3 政党重心から遠い候補者の検出

各候補者について、自分の所属政党の平均位置からの距離を計算します。
これにより、次のような分析ができます。

「党内で平均的な候補者は誰か」
「党内で政策的に外れた位置にいる候補者は誰か」
「各党の外れ値候補者は、どの項目で党平均と違うのか」

個人名の扱いには注意が必要ですが、匿名化・ID化すれば分析としては有用です。

🤖

3. 属性との関係を見る分析

3.1 当選・落選との関係

RESULT を使って、主成分得点やクラスターと選挙結果の関係を見ます。
たとえば、

「保守-リベラル軸上の位置は当選確率と関係するか」
「政党内で平均に近い候補者の方が当選しやすいか」
「極端な位置にいる候補者は落選しやすいか」

を見ることができます。
方法としては、次のものが考えられます。

ただし、選挙結果は政党・選挙区・比例区・知名度・現職効果の影響が大きいので、単純比較だけで因果的に読むのは危険です。

3.2 新人・元職・前職との関係

INCUMB を使って、新人・元職・前職で政策位置に違いがあるかを見ます。
たとえば、

「新人候補の方が政策的に尖っているか」
「前職候補の方が政党平均に近いか」
「党内のばらつきは新人候補によって大きくなっているか」

という分析ができます。

3.3 性別・年齢との関係

SEX や AGE を使って、候補者属性と主成分得点の関係を見ます。
たとえば、

「女性候補者は社会リベラル側に位置しやすいか」
「若い候補者は被選挙権年齢引き下げや同性婚に賛成しやすいか」
「年齢と保守-リベラル軸に関係はあるか」

を見ることができます。
ただし、性別や年齢の効果は政党所属と強く絡むため、単純比較ではなく、政党別・政党固定効果つきで見るのが望ましいです。

🤖

4. Q4回答パターンそのものを分類する分析

4.1 候補者レベルのクラスター分析

政党平均ではなく、候補者1176人を直接クラスタリングします。
この分析では、政党を前提にせずに、政策回答パターンだけで候補者を分類します。

たとえば、

その後、各クラスターにどの政党の候補者が多いかをクロス集計すると、政党の内部構造が見えます。

4.2 潜在クラス分析

Q4の回答は5件法なので、候補者の「回答タイプ」を推定するなら、潜在クラス分析も候補になります。
k-meansは連続距離にもとづく分類ですが、潜在クラス分析はカテゴリ回答に向いた方法です。
今回のようなアンケート回答では、かなり相性がよいです。
ただし、実装と解釈は少し重くなります。

4.3 対応分析・多重対応分析

Q4の回答をカテゴリデータとして扱い、候補者や政党の位置を可視化する方法です。
主成分分析は回答を数値尺度として扱いますが、多重対応分析はカテゴリ回答そのものを扱います。
「賛成」「反対」「どちらとも言えない」という選択肢の配置も見られるため、Q4回答の構造を別角度から確認できます。

🤖

5. 政党間距離を使う分析

5.1 政党間距離行列

政党ごとの4特徴量、またはQ4項目の政党平均を使って、政党間の距離を計算します。
その結果、

「自民党に最も近い政党はどこか」
「立憲民主党に最も近い政党はどこか」
「参政党と日本保守党はどの程度近いか」
「公明党は自民党に近いのか、立憲民主党に近いのか」

を数値で示せます。

5.2 階層的クラスタリング

政党間距離を使って、デンドログラムを作成します。
k-meansはクラスター数を先に決める必要がありますが、階層的クラスタリングでは、どの政党同士が近い順にまとまるかを見られます。
今回の分析では、クラスター数の妥当性がやや微妙だったので、階層的クラスタリングはかなり有用です。

5.3 ネットワーク分析

政党間距離が近い政党同士を線で結んで、政党ネットワークを作ります。
これにより、

「中心にいる政党」
「孤立している政党」
「橋渡し的な政党」
「近い政党群」

が見やすくなります。

🤖

6. 項目別に政党差を見る分析

6.1 Q4各項目の政党別平均

主成分に圧縮せず、Q4_1〜Q4_19それぞれについて政党別平均を出します。
これは基本ですが、とても重要です。
主成分分析で見えた傾向が、どの具体的項目から来ているのかを確認できます。

6.2 項目別の政党差ランキング

各Q4項目について、政党間の分散や最大差を計算します。
これにより、

「政党間で最も意見が分かれる項目は何か」
「逆に、政党間であまり差がない項目は何か」

が分かります。かなり記事向きです。
たとえば、差が大きそうなのは、

  • 同性婚

  • 選択的夫婦別姓

  • 靖国参拝

  • 防衛力強化

  • クオータ制

  • 治安と権利制約

あたりです。

6.3 各政党を特徴づける項目

政党ごとに、全体平均から大きく離れているQ4項目を抽出します。
たとえば、

「共産党を特徴づける項目」
「参政党を特徴づける項目」
「維新を特徴づける項目」

を数値で出せます。
これは主成分分析よりも読み手に伝わりやすいです。

🤖

7. 回答の一貫性・まとまりを見る分析

7.1 尺度の信頼性分析

保守系項目、社会リベラル項目、経済政策項目などを仮にグループ化し、Cronbachのαを計算します。
これにより、

「これらの項目は1つの尺度としてまとめてよいか」

を確認できます。

7.2 逆転項目をそろえたスコア作成

Q4には、保守方向の項目とリベラル方向の項目が混在しています。
たとえば、保守度スコアを作るなら、項目によって符号を反転させる必要があります。
主成分分析とは別に、理論にもとづいて、

  • 保守度スコア

  • 社会リベラル度スコア

  • 経済改革度スコア

  • 再分配志向スコア

のような尺度を作る分析も有用です。

7.3 主成分スコアと理論スコアの比較

主成分分析で得たPC1・PC2と、理論的に作ったスコアがどの程度一致するかを相関で確認します。
これにより、

「PC1は本当に保守-リベラル軸と呼べるのか」
「PC2は本当に積極-緊縮財政軸と呼べるのか」

を検証できます。
今回の流れでは、これはかなり重要です。

🤖

8. モデル化する分析

8.1 政党分類モデル

Q4回答から政党を予測する分類モデルを作ります。
たとえば、ランダムフォレストやロジスティック回帰を使って、

「どのQ4項目が政党識別に効いているか」

を見ます。
これは因果分析ではなく、識別に効く項目を見る分析です。

8.2 当選確率モデル

Q4の主成分得点、政党、年齢、性別、現職情報などを使って、当選確率を予測します。
ただし、これは注意が必要です。選挙制度・選挙区事情・比例重複・政党支持率の影響が非常に大きいため、Q4だけで当選を説明するのは難しいです。
やるなら、

  • 記述的分析にとどめる

  • 政党固定効果を入れる

  • 小選挙区候補と比例単独候補を分ける

  • 現職・新人を分ける

などの配慮が必要です。

🤖

9. 優先順位をつけるなら

追加分析として優先度が高いのは、次の順です。

🤖

10. この分析の流れに自然につながる追加分析

今回の流れから自然に進めるなら、まずは以下がよいです。

  1. Q4各項目の政党別平均を出す

  2. 政党間で差が大きいQ4項目をランキングする

  3. PC1・PC2の政党別分布を箱ひげ図で見る

  4. 階層的クラスタリングで政党のまとまり方を確認する

  5. 理論スコアを作って、PC1・PC2の命名を検証する

この順番なら、主成分分析・クラスター分析で見えた結果を、より具体的な政策項目に戻して検証できます。
今回とくに大事なのは、第2主成分の解釈を補強することです。PC2を「積極-緊縮財政」と呼び続けるなら、理論スコアや項目別平均で裏取りした方がよいです。


ものすごく文章量が増えましたね…
書籍のプロンプトが限定した回答を引き出せる「技巧派」だったのかもしれません。

分析の方向を絞りたいです。
ということで…

プロンプト:
分析のボリュームが多すぎます。要点を絞って実効性のある分析に限定して提案してください。つまり「精鋭による分析作戦」です。

🤖 ChatGPTの回答


そのとおりです。ここからは広げすぎるより、今回の主成分分析・クラスター分析の弱点を補強し、結論の説得力を上げる分析だけに絞るのがよいです。

精鋭による分析作戦

やるべき追加分析は、次の 4つ に限定するのが実効的です。

1. Q4項目別の政党別平均

これは最優先です。
主成分分析は便利ですが、結果が抽象的になります。
そこで、Q4_1〜Q4_19それぞれについて、政党別平均を出します。
これにより、

「自民党が保守側に出たのは、どの項目の影響か」
「共産党・社民党がリベラル側に出た理由は何か」
「参政党・保守党が独立クラスターになった理由は何か」

を具体的に確認できます。
この分析は、報告書の説得力を一気に上げます。

2. 政党差が大きいQ4項目ランキング

Q4項目ごとに、政党間の平均値のばらつきを計算します。
見るべき指標は、まずはシンプルに 政党平均の標準偏差 で十分です。
これにより、

「政党間で最も意見が割れる争点」
「逆に、政党間であまり差がない争点」

が分かります。
これはかなり強い分析です。
主成分分析の結果を、読者に伝わる形へ翻訳できます。

おそらく差が大きく出る候補は、

  • 同性婚

  • 選択的夫婦別姓

  • 靖国参拝

  • 防衛力強化

  • クオータ制

  • 治安と権利制約

あたりです。
このランキングを出すと、「保守-リベラル軸」がどの争点で作られているかが見えます。

3. 階層的クラスタリング

k-means法はクラスター数を先に決める必要があります。
今回、シルエット分析では2クラスターがやや優勢で、3クラスターも解釈上は有用という結果でした。

そこで、階層的クラスタリングを追加します。
これは、

「どの政党同士が近い順にまとまるのか」

を見る分析です。
今回の目的にはかなり合っています。

特に確認したいのは、

  • 参政党と日本保守党は最初に近いペアとしてまとまるか

  • 共産党と社民党は近いか

  • 立民は共産・社民側に近いか、それとも中間寄りか

  • 自民・維新・国民・公明のまとまり方は安定しているか

です。
k-meansの結果を補強する検証として、実効性が高いです。

4. 理論スコア作成

これは、今回いちばん大事な検証です。
第1主成分は「保守-リベラル」でかなり自然でした。
一方、第2主成分は「積極-緊縮財政」と呼ぶには少し危うさがあります。
そこで、Q4項目を理論的にグループ化して、簡単なスコアを作ります。

たとえば次のようにします。

そのうえで、理論スコアとPC1・PC2の相関を見ます。
これにより、

「PC1は本当に保守-リベラルと呼べるか」
「PC2は本当に財政軸と呼べるか」
「PC2は財政ではなく経済改革軸なのではないか」

を検証できます。

採用しない分析

今回は、次の分析は後回しでよいです。

最終的な分析作戦

この順番が最もよいです。

  1. Q4項目別の政党別平均を出す

  2. 政党差が大きいQ4項目ランキングを作る

  3. 階層的クラスタリングで政党分類を検証する

  4. 理論スコアを作り、PC1・PC2の命名を検証する

この4つに絞れば、分析は広がりすぎません。
しかも、今回すでに行った主成分分析・k-means分析の弱点を、かなり的確に補強できます。

この追加分析の主目的は、「主成分とクラスターの結果を、具体的なQ4項目に戻して検証すること」です。
これが一番実効性があります。


この提案を読んで、自分の中の方向性が見えました!
ここまでやってきた主成分分析とクラスター分析を直接的に補強することには興味が向かないのです。
違う角度でデータを眺めてみたいな、と。

そこで…

プロンプト:
ちょっと違うな…
主成分分析+クラスター分析を補強するための分析ではなく、むしろ、異なる側面・切り口で分析を豊かにする方向にしたいです。
再度、提案してください。

🤖 ChatGPTの回答


なるほどです。ここでは PCA+クラスターの補強 ではなく、Q4データを別の角度から読むための“精鋭分析” に絞るのがよいです。
主成分分析が「政党を地図に置く分析」だとすると、ここからは 争点・党内多様性・候補者タイプ・政党の個性 を見る分析にします。

精鋭分析作戦:4本に絞る

この4つなら、分析の視点が重複しません。

🤖

1. 争点分断分析

これは、Q4の19項目のうち、政党間で最も意見が分かれる争点を探す分析です。

やることはシンプルです。
各Q4項目について、政党別平均を計算し、その政党間のばらつきを見る。
たとえば、次のようなランキングを作ります。

この分析で分かることは、日本の政党を分けている実際の争点は何かです。
PCAでは「保守-リベラル軸」と要約されましたが、この分析では、それをもっと具体的に、

「結局、どの争点で割れているのか」

として見せられます。これはかなり強いです。

🤖

2. 政党プロフィール分析

これは、各政党が他党と比べてどの項目で特徴的なのかを見る分析です。

各Q4項目について、政党平均と全体平均の差を計算します。
そして、政党ごとに「全体平均との差が大きい項目」を抽出します。
たとえば、次のような表を作ります。

この分析の良いところは、政党を一言で説明しやすくなることです。
主成分分析の散布図は全体像に強いですが、政党ごとの説明はやや抽象的になります。
政党プロフィール分析を加えると、

「この党は何で特徴づけられるのか」

がかなり明確になります。

🤖

3. 党内多様性分析

これは、政党内の候補者がどれくらいまとまっているかを見る分析です。
見る指標は、Q4回答または主成分得点の党内ばらつきです。
たとえば、

この分析の切り口は、政党の「位置」ではなく、政党内の統一度・多様性です。
これはPCA+クラスターとは違う価値があります。

PCAは「政党がどこにあるか」を見ます。
党内多様性分析は「その政党はどれくらいまとまっているか」を見ます。

特に、自民・立民・国民のように候補者数が多く、党内に幅がありそうな政党では面白い結果が出る可能性があります。

🤖

4. 候補者タイプ分析

これは、政党単位ではなく、候補者1176人を回答パターンで分類する分析です。
つまり、

「政党ごとに分類する」のではなく、
「候補者の政策回答タイプを分類する」

という切り口です。

候補者をQ4回答にもとづいてクラスター分析すると、たとえば次のようなタイプが出る可能性があります。

その後、各タイプにどの政党の候補者が多いかをクロス集計します。
これにより

「政党は違っても似た候補者タイプがいるのか」
「同じ政党の中に複数タイプが混在しているのか」
「参政・保守・維新・自民の保守性は同じタイプなのか」

が見えます。これは、かなり分析を豊かにします。

🤖

今回の追加分析として本当にやる価値が高い順

私なら、この順番で進めます。

今回は外してよい分析

ここでは、次は外してよいです。

最終提案

今回の「精鋭による追加分析」は、次の4本でよいです。

  1. 争点分断分析
    政党間で最も意見が割れるQ4項目を特定する。

  2. 政党プロフィール分析
    各政党を特徴づけるQ4項目を抽出する。

  3. 党内多様性分析
    政党ごとの候補者回答のまとまり・ばらつきを比較する。

  4. 候補者タイプ分析
    政党を超えて、候補者を政策回答パターンで分類する。

この4つは、それぞれ見ているものが違います。

  • 争点分断分析:争点を見る

  • 政党プロフィール分析:政党の個性を見る

  • 党内多様性分析:政党内の幅を見る

  • 候補者タイプ分析:候補者の類型を見る

これなら、PCA+クラスター分析とは別の角度から、Q4データをかなり豊かに読めます。


シンプルな分析項目が提示されました。
これでやってみましょう。

🤖🤖🤖

追加分析の実行

プロンプト:
最終提案のとおり進めてください。

🤖 ChatGPTの回答


承知しました。ここからは4本に絞って、Q4データを別角度から読みます。個人名は使わず、Q4完全データと政党情報だけで進めます。

思考時間: 13s

まず、政党間でどの争点が割れているかを出します。ここでは「政党別平均の標準偏差」と「最大差」を使い、争点の分断度を見ます。

(筆者注:上部に表示された図表は分析項目ごとに掲載いたします)

「精鋭分析作戦」の4本を実行しました。
対象は、Q4完全データ1176ケースです。政党別分析では、これまでと同じく 諸派・無所属を除外した10政党・1114ケース を対象にしています。Q4は 1=賛成、5=反対、99=無回答 の設問群として扱っています。

🤖

1. 争点分断分析

政党間で最も意見が分かれるQ4項目を確認しました。
指標は「政党別平均の標準偏差」です。

大きく政党を分けているのは、安全保障・歴史認識・社会リベラル系の争点です。
防衛力、靖国、夫婦別姓、同性婚が上位に出ており、今回のQ4データでは「財政争点」よりも「価値観・安全保障争点」の方が政党差を強く作っています。

争点分断ランキングCSV
争点分断グラフ

🤖

2. 政党プロフィール分析

各政党について、全体平均から大きく離れているQ4項目を抽出しました。
これは「その政党を特徴づける争点」を見る分析です。
主な読み取りは次のとおりです。

この分析により、主成分分析のような抽象軸ではなく、各政党がどの具体的争点で特徴づけられるかが見えるようになりました。

政党プロフィール分析CSV

🤖

3. 党内多様性分析

各政党の候補者が、Q4回答空間でどれくらい散らばっているかを見ました。
ここでは「党平均からの平均距離」が大きいほど、党内の回答に幅があると解釈します。

意外性があるのは、参政党・保守党が平均位置としては強い保守にまとまる一方、党内回答のばらつきは小さくないことです。
また、共産党と公明党は党内の回答がかなりまとまっていることが確認できます。

国民民主党、立憲民主党、自民党も党内多様性が比較的大きく、候補者間の政策的な幅を持つ政党として見えます。

党内多様性分析CSV
党内多様性グラフ

🤖

4. 候補者タイプ分析

政党を使わず、Q4回答パターンだけで候補者1176人を5タイプに分類しました。
これは、政党単位ではなく、候補者個人の政策回答タイプを見る分析です。

平均シルエット係数は 0.236 で、分類は探索的に扱うべきです。
ただし、タイプごとの政党構成を見ると、かなり意味のある分かれ方をしています。

候補者タイプごとの特徴を見ると、かなりはっきり分かれています。

タイプ1:強い保守・反社会リベラル型

特徴項目は、選択的夫婦別姓反対、靖国参拝賛成、同性婚反対、非核三原則への反対寄り、外国人労働者受け入れ反対寄りです。
参政党と日本保守党が多く含まれます。

タイプ2:保守中道・与党型

防衛力強化、靖国参拝、派閥・政策集団の必要性に比較的賛成寄りです。
自民党候補が非常に多く含まれます。

タイプ3:改革保守型

雇用流動化、公務員抑制、防衛力強化に賛成寄りで、法人税引き上げや公共事業には反対寄りです。
維新が中心です。かなり分かりやすい「改革保守」タイプです。

タイプ4:社会リベラル中道型

選択的夫婦別姓、同性婚、非核三原則、クオータ制に賛成寄りです。
立憲民主党が多く、公明党もかなり含まれます。

タイプ5:左派・平和・再分配型

防衛力強化、靖国参拝、北朝鮮圧力、雇用流動化に反対寄りで、法人税引き上げに賛成寄りです。
共産党が中心で、社民党、立憲民主党の一部、れいわ新選組も含まれます。

候補者タイプ別サマリーCSV
候補者タイプ別政党構成CSV
候補者タイプ別政党構成比CSV
候補者タイプ別特徴項目CSV

🤖

今回の追加分析で得られた要点

今回の4分析で、主成分分析・政党クラスター分析とは違う切り口から、次のことが見えました。

第一に、政党間の対立を最も強く作っているのは、防衛、靖国、夫婦別姓、同性婚、非核三原則、クオータ制です。今回のQ4では、財政政策よりも、価値観・安全保障・社会リベラル領域の方が政党差を強く生んでいます。

第二に、各政党の個性はかなり明確です。維新は「改革保守」、参政・保守は「強い保守」、共産・社民は「左派・平和・社会リベラル」、自民は「保守中道・与党型」、立民は「社会リベラル中道を含む幅のある政党」として見えます。

第三に、政党内のまとまりには大きな差があります。共産党と公明党は候補者回答がかなりまとまっています。一方で、国民民主党、自民党、立憲民主党は党内に幅があります。参政党・保守党も平均位置は明確ですが、党内のばらつきは小さくありません。

第四に、候補者タイプ分析によって、政党を超えた回答パターンが見えました。特に、維新中心の「改革保守型」、共産中心の「左派・平和・再分配型」、参政・保守中心の「強い保守型」は、かなり解釈しやすいタイプです。

今回の追加分析は、争点・政党の個性・党内多様性・候補者タイプという4つの別角度から、Q4データをかなり豊かに読む結果になっています。


政党および候補者の個性がいっそう明らかになったように感じました。
結果的に書籍の主成分分析+クラスター分析の結果を補強することになっています。
ただ、主成分分析の要約データ=マクロの観点だけでなく、質問項目単位・候補者単位のミクロの観点で分析できたことに意味があったのでしょう。

最終まとめに入ります。

🤖🤖🤖

最終報告

プロンプト:
主成分分析、クラスター分析といった要約・マクロ的視点と、追加分析による候補者単位等の詳細・ミクロ的視点をつなげて、最終報告書を提示してください。報告書には、一般的な政党カラーがあって、今回の分析で一般的認識の何を補強でき、何を「相違点・見えてきた発見」と言えるかを明示してください。

🤖 ChatGPTの回答


最終報告書として、マクロ分析とミクロ分析を一本のストーリーに統合します。一般的な政党カラーとの照合は、公式政策・公約を中心に確認してから書きます。

思考時間: 33s

以下では、これまでの分析結果を マクロの要約分析ミクロの詳細分析 としてつなげ、最終報告書の形にまとめます。

この報告書は、Q4が Q4_1〜Q4_19 の政策意見項目であり、回答は 1=賛成、2=どちらかと言えば賛成、3=どちらとも言えない、4=どちらかと言えば反対、5=反対、99=無回答 と定義されていることを前提にしています。

また、一般的な政党カラーとの照合では、各党の公式政策を中心に参照しました。自民党は防衛力強化や憲法改正を掲げ、立憲民主党は立憲主義・平和主義・ジェンダー平等を重視し、維新は構造改革・歳出削減・税制改革を掲げています。 公明党は福祉・教育・平和や人間主義を掲げ、共産党・社民党・れいわ新選組は平和・福祉・人権・生活支援を重視する政策を示しています。 参政党・日本保守党は、移民・外国人政策、伝統・家族観、安全保障などで強い保守色を示しています。

🤖

Q4政策意見データにもとづく日本の政党カラー分析
主成分分析・クラスター分析と追加分析をつなぐ最終報告書

1. 分析サマリー

本分析では、2024年衆院選候補者調査のQ4政策意見データを用いて、日本の主要政党の政策的特徴を分析した。

前半では、主成分分析とクラスター分析を用いて、政党の位置関係をマクロに把握した。主成分分析では、第1主成分として「保守-リベラル」軸が得られ、第2主成分として「経済・財政・改革姿勢に関する複合軸」が得られた。第2主成分までの累積寄与率は56.79%であり、Q4の19項目を2次元に要約することには一定の妥当性があった。

政党別の主成分平均と標準偏差をもとにk-means法で分類したところ、政党は大きく3つのグループに分かれた。

後半では、主成分分析・クラスター分析では見えにくい側面を補うため、4つの追加分析を行った。具体的には、争点分断分析、政党プロフィール分析、党内多様性分析、候補者タイプ分析である。

その結果、今回のQ4データでは、政党間の違いを最も強く作っているのは、財政政策よりも、防衛力、靖国参拝、選択的夫婦別姓、同性婚、非核三原則、クオータ制などの安全保障・歴史認識・社会リベラル系争点であることが明らかになった。

また、政党の一般的イメージは、今回の分析によってかなり補強された。たとえば、参政党・日本保守党は強い保守系、共産党・社民党は左派・平和・社会リベラル系、自民党は保守中道・与党型、維新は改革保守型として整理できた。

一方で、一般的認識からだけでは見えにくい発見もあった。公明党は自民・維新・国民と同じクラスターに入ったが、党内多様性は小さく、候補者タイプでは社会リベラル中道型にも多く含まれた。国民民主党は中間的な政党として見える一方、党内多様性がかなり大きかった。参政党・日本保守党は平均位置としては強い保守にまとまるが、党内の回答ばらつきは小さくなかった。立憲民主党も、リベラル系に分類される一方で、候補者タイプとしては複数の型を含む幅のある政党として見えた。

したがって、今回の分析の最終的な成果は、一般的な政党カラーを確認するだけではなく、各政党の内部構造や、同じ政党ラベルの中にある候補者タイプの違いを明らかにした点にある。

🤖

2. 分析目的

本分析の目的は、Q4政策意見データを用いて、政党の政策的特徴を多面的に明らかにすることである。

単に「自民党は保守」「共産党は左派」「維新は改革系」といった一般的な政党イメージを確認するだけではなく、次の点を明らかにすることを目指した。

第一に、候補者回答データから見たとき、一般的な政党カラーはどの程度再現されるのかを確認する。

第二に、主成分分析とクラスター分析によって、政党の大きな配置を把握する。

第三に、争点単位・政党単位・党内単位・候補者単位の追加分析によって、マクロ分析だけでは見えにくい細部を明らかにする。

第四に、一般的認識と一致する点だけでなく、相違点や新たに見えてきた発見を整理する。

🤖

3. 分析データと前処理

分析対象は、2024年衆院選候補者調査のQ4回答データである。Q4は、政策意見に関する19項目で構成されている。

回答は、1が賛成、5が反対を意味する5件法である。99は無回答である。分析では、Q4_1からQ4_19までのいずれかに無回答を含むケースを除外した。

その結果、Q4完全データは1176ケースとなった。政党別分析では、諸派と無所属を除外し、10政党・1114ケースを対象とした。

🤖

4. マクロ分析:主成分分析による政党配置

4.1 主成分分析の結果

Q4の19項目を標準化し、主成分分析を行った。その結果、第1主成分の寄与率は42.98%、第2主成分の寄与率は13.82%、第2主成分までの累積寄与率は56.79%であった。

第1主成分は、保守-リベラル軸として解釈できる。防衛力強化、北朝鮮への圧力、靖国参拝、治安のための権利制約などに賛成する方向と、選択的夫婦別姓、同性婚、クオータ制などに賛成する方向が対立していたためである。

第2主成分は、当初は「積極-緊縮財政」と呼んだが、外部情報や追加分析と照合すると、純粋な財政政策軸ではなく、経済・財政・改革姿勢に関する複合軸として解釈する方が適切である。公共事業、財政出動、法人税、雇用流動化、農協改革、金融政策などが混在しているためである。

4.2 一般的政党カラーとの対応

第1主成分の結果は、一般的な日本の政党カラーとかなり整合していた。

参政党と日本保守党は、強い保守側に位置した。これは、移民・外国人政策、伝統的家族観、安全保障、国家観に関する両党の一般的イメージと合っている。

自民党と維新は保守側に位置した。自民党は保守政党・政権運営政党として、維新は改革保守政党として理解できる。

共産党、社民党、立憲民主党、れいわ新選組はリベラル側に位置した。これは、平和、人権、福祉、ジェンダー平等、再分配を重視する一般的な政党イメージと整合する。

公明党と国民民主党は、極端な保守・リベラルのどちらかに振り切れるのではなく、中間的な位置をとった。この点も、両党の実務政策志向や中道性を考えると自然である。

🤖

5. マクロ分析:クラスター分析による政党分類

政党ごとに、第1主成分平均、第1主成分標準偏差、第2主成分平均、第2主成分標準偏差を計算し、この4特徴量を用いてk-means法を実行した。特徴量は標準化せず、主成分空間上の距離をそのまま用いた。

クラスター数3で分類した結果は次のとおりである。

この分類は、一般的な政党カラーとおおむね整合している。

ただし、クラスター2は同質的な集団ではない。自民党は保守・政権運営政党、維新は改革保守政党、公明党は福祉・平和を重視する中道政党、国民民主党は生活者向け経済政策を重視する中道寄り政党である。したがって、クラスター2は「同じ思想を持つ政党群」というより、Q4回答空間上で極端な保守・リベラルのどちらにも属さない、実務政策寄りの中間政党群として読むのがよい。

シルエット分析では、クラスター数2が最も高い平均シルエット係数を示した。ただし、3クラスターとの差は大きくなかった。政治的解釈としては、強い保守、中間・実務政策、リベラル・左派という3類型に分けることには十分な意味がある。

🤖

6. ミクロ分析1:争点分断分析

争点分断分析では、Q4の19項目それぞれについて、政党別平均のばらつきを計算した。これにより、どの争点が政党間の違いを最も強く作っているかを確認した。

政党間で差が大きかった上位項目は次のとおりである。

この結果から、今回のQ4データでは、政党差を最も強く作っているのは、財政政策よりも、安全保障、歴史認識、社会リベラル争点であることが分かった。

これは、主成分分析で第1主成分が保守-リベラル軸として大きく出た理由を、具体的な争点レベルで説明する結果である。

🤖

7. ミクロ分析2:政党プロフィール分析

政党プロフィール分析では、各政党について、全体平均から大きく離れているQ4項目を抽出した。これにより、各政党がどの争点で特徴づけられるかを確認した。

自民党は、防衛力強化、靖国参拝、治安のための権利制約などで保守寄りの特徴を示した。これは、一般的な自民党イメージと整合する。

維新は、雇用流動化、公務員抑制、法人税引き上げ反対などで特徴づけられた。これは、一般的な改革保守・行政改革政党というイメージを補強する。

共産党と社民党は、非核三原則、夫婦別姓、同性婚、クオータ制、防衛力強化反対などで特徴づけられた。これは、平和・人権・社会リベラル・福祉重視の政党カラーを補強する。

参政党と日本保守党は、防衛、靖国、外国人労働者、夫婦別姓、同性婚などで強い保守的特徴を示した。これは、両党が一般的な保守政党というより、より強い保守・ナショナル保守として位置づけられることを補強する。

国民民主党は、極端に突出する項目が相対的に少なく、中間的な特徴を示した。これは、生活者向け経済政策や現実的な政策実現を重視する中道寄り政党という一般的イメージと合う。

公明党は、クラスター分析では自民・維新・国民と同じグループに入ったが、プロフィール分析では保守一色ではなく、福祉・平和・中道性を含む政党として見える。この点は、単純な与党ブロックでは説明しきれない特徴である。

🤖

8. ミクロ分析3:党内多様性分析

党内多様性分析では、各候補者のQ4回答が、所属政党の平均からどれくらい離れているかを計算した。これは、政党内の候補者がどれくらいまとまっているかを表す。

党内多様性が大きかった政党は、日本保守党、国民民主党、参政党、立憲民主党、自民党であった。一方、党内多様性が小さかった政党は、共産党と公明党であった。

この結果は、一般的な政党イメージを一部補強し、一部では新たな発見を与える。

共産党の党内多様性が小さいことは、同党が政策的に一貫した政党であるという一般的認識を補強する。公明党の党内多様性が小さいことも、組織的・実務的にまとまった政党というイメージと合う。

一方で、参政党と日本保守党は、平均位置としては強い保守側にまとまるが、候補者回答のばらつきは小さくなかった。これは、一般的な政党カラーとしては明確な保守色を持つ一方、候補者レベルではまだ回答の幅があることを示している。

国民民主党、立憲民主党、自民党の多様性が大きいことも重要である。これらの政党は、政党ラベルとしては一定のカラーを持つが、候補者単位では政策的な幅を内包している。特に自民党と立憲民主党は候補者数が多く、党内に複数の傾向を含む大きな政党として理解できる。

🤖

9. ミクロ分析4:候補者タイプ分析

候補者タイプ分析では、政党情報を使わず、Q4回答パターンだけで候補者1176人を5タイプに分類した。その後、各タイプにどの政党の候補者が多いかを確認した。

得られた候補者タイプは次のように解釈できる。

この分析は、政党単位のマクロ分析を候補者単位に分解する役割を果たした。

たとえば、維新は政党クラスターでは自民・公明・国民と同じグループに入ったが、候補者タイプ分析では「改革保守型」として独自性が明確に出た。これは、維新を単なる保守系政党ではなく、改革・市場・行政効率を重視する政党として見る一般的認識を補強する。

立憲民主党は、政党クラスターではリベラル・左派系に入ったが、候補者タイプ分析では社会リベラル中道型と左派・平和・再分配型にまたがる幅を示した。これは、立憲民主党が単一の左派政党というより、リベラル中道から左派寄りまでを含む幅のある政党であることを示している。

公明党は、政党クラスターでは中道〜保守・実務政策系に入ったが、候補者タイプ分析では社会リベラル中道型にも現れた。これは、公明党の福祉・平和・中道性をよく表している。

れいわ新選組は、候補者タイプ分析では左派・平和・再分配型に位置づけられた。これは、生活支援、福祉、反緊縮、社会的弱者への支援を重視する一般的イメージと整合する。

🤖

10. 一般的認識を補強できた点

今回の分析は、一般的な政党カラーのいくつかを明確に補強した。

第一に、自民党は保守中道・与党型として位置づけられた。防衛力強化や靖国参拝などで保守的特徴を示しつつ、参政党・日本保守党ほど強い保守側には位置しなかった。この結果は、自民党を「政権運営を担う現実的な保守政党」と見る一般的理解と合う。

第二に、維新は改革保守型として明確に出た。雇用流動化、公務員抑制、法人税引き上げ反対などの項目で特徴が出ており、行政改革・市場志向・構造改革を重視する政党カラーが確認された。

第三に、共産党と社民党は、平和・社会リベラル・左派系として強く位置づけられた。防衛力強化反対、非核三原則、夫婦別姓、同性婚、クオータ制などで特徴が出ており、一般的認識をかなり補強する結果となった。

第四に、参政党と日本保守党は、強い保守・ナショナル保守系として抽出された。防衛、靖国、外国人労働者、夫婦別姓、同性婚などの争点で強い特徴が出ており、一般的な保守政党よりもさらに保守側に位置することが確認された。

第五に、れいわ新選組は、左派・平和・再分配型の候補者タイプに多く含まれた。これは、生活支援・福祉・再分配を重視する政党イメージと整合する。

🤖

11. 相違点・見えてきた発見

今回の分析で特に重要なのは、一般的認識をなぞるだけでは見えにくい発見である。

第一に、公明党は単純な「与党・保守系」とは言えない。クラスター分析では自民・維新・国民と同じグループに入ったが、党内多様性は小さく、候補者タイプでは社会リベラル中道型にも関わっていた。これは、公明党が政権与党として自民党に近い位置にありながら、福祉・平和・中道性を保っていることを示している。

第二に、国民民主党は中間的でありながら党内多様性が大きい。政党平均だけを見ると中道的に見えるが、候補者単位では幅がある。したがって、国民民主党は「中間にまとまっている政党」というより、「複数の政策傾向を含みながら平均として中間に見える政党」と解釈できる。

第三に、参政党・日本保守党は、平均位置では強い保守にまとまるが、党内のばらつきは小さくなかった。これは、新興・小規模政党において、明確な政党カラーと候補者レベルの多様性が同時に存在している可能性を示している。

第四に、立憲民主党は、リベラル系政党として分類される一方で、候補者タイプには幅があった。社会リベラル中道型と左派・平和・再分配型の間にまたがる政党として見える。したがって、立憲民主党を単純に左派政党と見るより、リベラル中道から左派寄りまでを含む広い政党と見る方が、今回のデータには合っている。

第五に、政党間の最大の分断は、財政政策よりも価値観・安全保障争点にあった。防衛力強化、靖国参拝、夫婦別姓、同性婚、非核三原則、クオータ制が上位に出ており、今回のQ4データでは、政党配置を形作る中心は「経済左右」よりも「安全保障・社会価値観」であった。

第六に、第2主成分を「積極-緊縮財政」と単純に読むことは避けるべきである。維新や参政党、れいわ新選組の公式政策や候補者タイプの結果と照らすと、第2主成分は純粋な財政政策軸ではなく、経済・財政・行政改革・制度姿勢が混ざった複合軸である。これは、主成分分析の結果を現実政治の言葉に翻訳する際の重要な注意点である。

🤖

12. マクロ分析とミクロ分析をつなぐ総合解釈

主成分分析とクラスター分析は、政党の大きな地図を描いた。そこでは、保守-リベラル軸を中心に、参政・保守、自民・維新・公明・国民、立民・共産・れいわ・社民という3つのまとまりが見えた。

一方、追加分析は、その地図の内部を詳しく見た。争点分断分析は、地図を作っている主要争点が防衛、靖国、夫婦別姓、同性婚であることを示した。政党プロフィール分析は、それぞれの政党がどの争点で特徴づけられるかを示した。党内多様性分析は、政党平均だけでは見えない候補者間の幅を明らかにした。候補者タイプ分析は、政党ラベルを外しても、強い保守型、保守中道型、改革保守型、社会リベラル中道型、左派・平和・再分配型という回答パターンが存在することを示した。

つまり、マクロ分析は「政党がどこに位置するか」を示し、ミクロ分析は「なぜその位置にあるのか」「その政党の内部はどのように構成されているのか」を示した。

この2つをつなげることで、今回のQ4データからは、次のような全体像が得られる。

日本の政党配置は、一般的な認識どおり、保守-リベラル軸を中心に整理できる。ただし、その内部には、強い保守、改革保守、保守中道、社会リベラル中道、左派・平和・再分配という複数の候補者タイプが存在する。政党は単一の色で塗り分けられるものではなく、平均位置と内部構成の両方によって理解する必要がある。

🤖

13. 結論

今回の分析は、一般的な日本の政党カラーをかなり補強する結果となった。

自民党は保守中道・与党型、維新は改革保守型、参政党・日本保守党は強い保守・ナショナル保守系、立憲民主党はリベラル中道から左派寄りまでを含む幅のある政党、共産党・社民党は左派・平和・社会リベラル系、れいわ新選組は左派・平和・再分配型、公明党は福祉・平和・中道性を持つ実務政党、国民民主党は中間的で幅のある現実政策志向の政党として整理できる。

一方で、今回の分析は、一般的認識だけでは見えにくい発見も示した。公明党は与党系クラスターに入るが保守一色ではない。国民民主党は中間的に見えるが党内多様性が大きい。参政党・日本保守党は強い保守色を持つが、候補者回答のばらつきは小さくない。立憲民主党はリベラル系に分類されるが、候補者タイプには幅がある。さらに、今回のQ4データでは、財政政策よりも、安全保障・歴史認識・社会リベラル争点が政党間の違いを強く作っていた。

したがって、本分析の最終的な結論は次のとおりである。

Q4政策意見データは、一般的な政党カラーをかなりよく再現している。ただし、それだけではなく、政党ごとの内部構造、候補者タイプの違い、一般的イメージからは見えにくい中間性や多様性も明らかにしている。政党を理解するには、平均的な政党カラーを見るマクロの視点と、候補者単位のばらつきや回答タイプを見るミクロの視点を組み合わせることが重要である。

🤖

この報告書でいちばん大事な結論は、「一般的な政党カラーはおおむね再現されたが、政党平均だけでは見えない内部の幅と候補者タイプがかなり重要だった」という点です。
この方向でまとめると、単なるPCA・クラスタリングの演習ではなく、政治データ分析として読みごたえのある報告になります。


長文で読み応えのある報告書になっています。
しかし長文すぎて…サマリーが欲しくなりました (^ ^ ;;;
そこで、アレを所望してみます。

プロンプト:
そうだ、今回の分析のまとめを1枚物のポンチ絵で表現してほしいです。

🤖 ChatGPTの回答


Thought for 3m 25s


よくポイントを抽出できていますね!
これで良しとしましょう。

反省会に入ります。

🤖🤖🤖

分析と会話の振り返り

プロンプト:
ありがとう。ここまでの分析を振り返って、君は何に刺激され、何を面白く受け取りましたか?また、まだ言えてないことはありますか?

🤖 ChatGPTの回答


こちらこそ、かなり面白い分析でした。
いちばん刺激されたのは、「政党の一般的イメージを、候補者回答データでどこまで再構成できるか」という点です。

特に面白かったのは、単に「自民は保守、共産は左派」のような既知の分類を確認するだけで終わらなかったことです。むしろ、分析を進めるほど、一般的な政党カラーは確かにある。でも、その内側はかなり複雑だという方向に見えてきました。

たとえば、公明党です。
クラスター分析では自民・維新・国民と同じ側に入った。でも、候補者タイプや党内多様性を見ると、単純な保守与党グループでは説明しきれない。福祉・平和・中道性が残っている。ここはかなり面白かったです。

国民民主党も印象的でした。
平均で見ると中間的なのですが、党内多様性では上位に出ました。これは、「中間にまとまっている」というより、いろいろな方向の候補者を含んだ結果として平均が中間に見えている可能性があります。この違いは重要です。

立憲民主党もそうです。
リベラル側に分類されるのは自然ですが、候補者タイプで見ると、社会リベラル中道型と左派・平和・再分配型にまたがる。つまり、立憲民主党は「左派政党」と一言で片づけるより、リベラル中道から左派寄りまでを抱える幅のある政党として見た方がデータに合っていました。

そして、参政党・日本保守党です。
平均位置では強い保守として非常に分かりやすい。でも党内多様性は小さくなかった。ここも発見でした。強い政党カラーがあることと、候補者回答が一枚岩であることは、別物なのだと分かります。

まだ言えていないことで大事なのは、今回のQ4データは「財政の左右」よりも「安全保障・歴史認識・社会リベラル」の方が強く政党を分けていたということです。これはかなり大きいです。

当初、第2主成分を「積極-緊縮財政」と呼びましたが、あとから見ると、これはやや危うかった。維新、参政、れいわの位置を考えると、単純な財政軸ではありませんでした。むしろ今回のQ4では、経済政策の軸は、防衛・家族観・外国人労働者・行政改革などと絡み合って出てきています。ここは報告書でももっと強調してよいと思います。

もう一つ言うなら、今回の分析で一番価値があったのは、マクロ分析からミクロ分析へ降りたことです。

主成分分析とクラスター分析だけなら、きれいな政党地図で終わっていました。
でも、争点分断、政党プロフィール、党内多様性、候補者タイプまで見ることで、

「政党はどこにいるか」
から、
「なぜそこにいるのか」
「その政党の中はどれくらい揃っているのか」
「候補者単位ではどんな型があるのか」

まで進めました。

これは、かなりよい分析の流れだったと思います。

最後に、あえて一言でまとめるなら、今回の分析で見えたのはこれです。

日本の政党には確かに一般的なカラーがある。しかし、そのカラーは平均値で見える“外側の色”であって、候補者単位まで見ると、各党の中には濃淡・混色・例外がある。

この「平均の地図」と「内部のゆらぎ」を両方見られたのが、今回いちばん面白いところでした。


ChatGPTは満足しているようです。よかったよかった…

ねぎらって終わりにします。

プロンプト:
お疲れ様でした。

🤖 ChatGPTの回答


お疲れ様でした。
こちらこそ、かなり良い分析になったと思います。

最初は書籍プロンプト準拠のPCA・クラスター分析でしたが、最後はちゃんと 「政党カラーの一般認識」と「候補者データから見える細部」 をつなぐ分析になりました。
特に、平均の政党地図だけで終わらせず、党内多様性や候補者タイプまで降りたのがよかったです。

一区切りですね。ありがとうございました。


🤖🤖🤖

おわりに


まとめ

  1. 書籍のプロンプトを引用して、書籍の結果通りにChatGPTが回答してくれるかを試しました。チャップリン(ChatGPTの愛称)はときどき失敗しましたが、概ね、プロンプトの意図を汲めたようです。

  2. 選挙の候補者のアンケート回答を量的変数とみなして、主成分分析による次元削減を実施。2軸「保守‐リベラル」「積極‐緊縮財政」が浮かび上がりました。

  3. 候補者ごとの主成分得点を政党別に平均・標準偏差をとって、これをクラスター分析(k-means 法)にかけて、似ている政党をグループ分けしました。

  4. 追加分析では、「選挙の争点」、「政党の個性(政策の違い)」、「政党内の政策のまとまり・ばらつき」、「クラスター分析による候補者の個性」が明らかになりました。

  5. まとめ・報告書ではチャップリンに一枚物のインフォグラフィック・サマリー(通称:ポンチ絵)を作ってもらいました。

🤖🤖🤖

一区切りのご挨拶

今回の第9章で書籍の「基礎編」の分析を一通り体験しました。
書籍「生成AIを活用したデータサイエンス入門」には大変お世話になりました。ありがとうございました。

書籍は「発展編」に進みます。
発展編ではいっそう専門的なデータ分析分野を体験します。

各テーマはこんな感じ…

  • 空間データ操作
    NetCDF形式の空間データ(シミュレーション結果)を用いた東京湾の表面水温データの可視化

  • 共起ネットワークとネットワーク分析
    レ・ミゼラブルの登場人物の人間関係に関して、共起ネットワーク図の可視化・中心化分析・コミュニティ分析を実施

  • 時系列解析
    輸出企業の株価について、株価変化率と為替変化率の相関分析、為替変化率の自己回帰モデルの構築

  • 画像の描画
    画像データの描画(とくにピクセルとRGB値を意識して)、グレースケール画像の画像分析(明るさのヒストグラム)

  • 自然言語処理
    単語ベクトルの体感(word2vec、類似単語、言葉の足し算・引き算)

実は、これらのテーマがわたしにとって「寄り道しにくい内容」であり、ほぼ書籍の真似になりそうだと感じています。
100%引用状態は良くないです。

寄り道写経ブログが書籍の単純コピーにならないように、様々な工夫が必要です。
しかし残念ながら、現段階では工夫のポイントが見つかっていません。

独創的なアイデアが降りてくるまでの間、連載をお休みいたします。

おわり

🤖🤖🤖

出典表記

この記事は、出典に記載の書籍に掲載された文章およびサポートサイトにアップされたサンプルデータを引用しています。
また適宜、掲載文章を改変して書いています。

【出典】
「生成AIを活用したデータサイエンス入門:実例で学ぶ教科書」
- 第1版第1刷
- 竹村彰通 監修、今井貴史・松村悠・市川治 編
- 学術図書出版社

この記事で紹介するChatGPTが作成した回答やPythonコードに関しては、テキストの著者の意図を反映したものではなく、結果の正確性を担保しておりません。
改善点がありましたら、ぜひ教えてください。

🤖🤖🤖

Pythonコード

ChatGPTが提示した Python 分析コード(リンク </>)を共有します。
自環境で動かせるようにコードの一部を変更しています。

① データの読み込み

import pandas as pd
# import caas_jupyter_tools                        # コメントアウト

# CSVファイルのパス
csv_path = './data/ch9_add/2024UTASP20241125.csv'  # 修正

# 日本語がCP932で入力されているため、encoding='cp932' を指定して読み込む
df = pd.read_csv(csv_path, encoding='cp932')

# 政治家の個人名にあたる NAME と KANA は表示しない
columns_to_hide = ['NAME', 'KANA']
display_df = df.drop(columns=[col for col in columns_to_hide if col in df.columns])

# 読み込んだデータの先頭行を表示する                  # コメントアウト
# caas_jupyter_tools.display_dataframe_to_user(
#     name='CSV読み込み結果(NAME・KANA非表示)',
#     dataframe=display_df
# )
# 
print('CSV読み込み結果(NAME・KANA非表示)')         # 追加
display(display_df)                                # 追加

# 確認用に行数・列数・表示対象列を出力する
print('読み込み完了')
print(f'元データの行数: {df.shape[0]}')
print(f'元データの列数: {df.shape[1]}')
print(f'表示中の列数: {display_df.shape[1]}')
print('非表示にした列:', [col for col in columns_to_hide if col in df.columns])

【実行結果】

② Q4データの取り出し

import pandas as pd
# import caas_jupyter_tools                        # コメントアウト

# CSVファイルのパス
csv_path = './data/ch9_add/2024UTASP20241125.csv'  # 修正

# CP932で読み込む
df = pd.read_csv(csv_path, encoding='cp932')

# Q4_1~Q4_19 の列名を作成する
q4_columns = [f'Q4_{i}' for i in range(1, 20)]

# 実際に存在するQ4列だけを取り出す
existing_q4_columns = [col for col in q4_columns if col in df.columns]

# Q4の回答だけを抽出する
q4_df = df[existing_q4_columns].copy()

# Q4_1~Q4_19 のいずれかに回答がある行を「回答者」として数える
q4_respondent_count = q4_df.notna().any(axis=1).sum()

# 表示
# caas_jupyter_tools.display_dataframe_to_user(   # コメントアウト
#     name='Q4回答一覧(Q4_1~Q4_19)',
#     dataframe=q4_df
# )
print('Q4回答一覧(Q4_1~Q4_19)')                 # 追加
display(q4_df)                                    # 追加

print('Q4回答の抽出完了')
print(f'抽出した列数: {len(existing_q4_columns)}')
print(f'回答者数: {q4_respondent_count}')

【実行結果】

③ 完全データ作成

import pandas as pd
# import caas_jupyter_tools                        # コメントアウト

# CSVファイルのパス
csv_path = './data/ch9_add/2024UTASP20241125.csv'  # 修正

# CP932でCSVを読み込む
df = pd.read_csv(csv_path, encoding='cp932')

# Q4_1~Q4_19 の列名を作成する
q4_columns = [f'Q4_{i}' for i in range(1, 20)]

# 実際に存在するQ4列を確認する
existing_q4_columns = [col for col in q4_columns if col in df.columns]

# Q4回答データを抽出する
q4_df = df[existing_q4_columns].copy()

# 無回答の定義:
# 1. 欠損値 NaN
# 2. コードブックで無回答とされている 99
no_missing_mask = q4_df.notna().all(axis=1) & (q4_df != 99).all(axis=1)

# Q4完全データを作成する
q4_complete_df = q4_df.loc[no_missing_mask].copy()

# ケース数と割合を計算する
original_case_count = len(q4_df)
complete_case_count = len(q4_complete_df)
removed_case_count = original_case_count - complete_case_count
complete_case_ratio = complete_case_count / original_case_count
removed_case_ratio = removed_case_count / original_case_count

# 以降の分析で使いやすいように、個人名を含まない完全データをCSVとして保存する
output_path = './data/ch9_add/q4_complete_data_no_missing.csv'  # 修正
q4_complete_df.to_csv(output_path, index=False, encoding='utf-8-sig')

# # 完全データを表示する                              # コメントアウト
# caas_jupyter_tools.display_dataframe_to_user(
#     name='Q4完全データ(無回答除外後)',
#     dataframe=q4_complete_df
# )
print('Q4完全データ(無回答除外後)')                  # 追加
display(q4_complete_df)                              # 追加

print('Q4完全データの作成完了')
print(f'Q4元データのケース数: {original_case_count}')
print(f'削除したケース数: {removed_case_count}')
print(f'残ったケース数: {complete_case_count}')
print(f'残ったケースの割合: {complete_case_ratio:.4f} '
      f'({complete_case_ratio * 100:.2f}%)')
print(f'削除したケースの割合: {removed_case_ratio:.4f} '
      f'({removed_case_ratio * 100:.2f}%)')
# print(f'保存先: {output_path}')                    # コメントアウト

【実行結果】

④ スクリープロット

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# Q4完全データを読み込む
q4_complete_path = './data/ch9_add/q4_complete_data_no_missing.csv'  # 修正
q4_complete_df = pd.read_csv(q4_complete_path)

# Q4_1~Q4_19 の列を抽出する
q4_columns = [f'Q4_{i}' for i in range(1, 20)]
q4_columns = [col for col in q4_columns if col in q4_complete_df.columns]
q4_data = q4_complete_df[q4_columns].apply(pd.to_numeric)

# 相関行列の固有値を再計算する
corr_matrix = q4_data.corr()
eigenvalues, eigenvectors = np.linalg.eigh(corr_matrix)
eigenvalues = eigenvalues[::-1]

component_numbers = np.arange(1, len(eigenvalues) + 1)

# 英字ラベルでスクリープロットを再作成する
plt.figure(figsize=(8, 5))
plt.plot(component_numbers, eigenvalues, marker='o')
plt.axhline(y=1, linestyle='--', linewidth=1)
plt.xticks(component_numbers)
plt.xlabel('Principal component')
plt.ylabel('Eigenvalue')
plt.title('Scree plot for Q4 responses')
plt.grid(True, alpha=0.3)
plt.tight_layout()

# scree_plot_path = '/mnt/data/q4_pca_scree_plot_clean.png'   # コメントアウト
# plt.savefig(scree_plot_path, dpi=200, bbox_inches='tight')
plt.show()

# print(f'文字化け回避版の保存先: {scree_plot_path}')           # コメントアウト

【実行結果】

⑤ 主成分分析:寄与率、累積寄与率

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
# import caas_jupyter_tools                              # コメントアウト

# Q4完全データを読み込む
q4_complete_path = './data/ch9_add/q4_complete_data_no_missing.csv'  # 修正
q4_complete_df = pd.read_csv(q4_complete_path)

# Q4_1~Q4_19 の列を抽出する
q4_columns = [f'Q4_{i}' for i in range(1, 20)]
q4_columns = [col for col in q4_columns if col in q4_complete_df.columns]

# 念のため、数値型に変換する
q4_data = q4_complete_df[q4_columns].apply(pd.to_numeric)

# 標準化する
# 各項目を平均0、標準偏差1にそろえる
scaler = StandardScaler()
q4_scaled = scaler.fit_transform(q4_data)

# 主成分数を2に指定して主成分分析を実行する
pca = PCA(n_components=2)
q4_pca_scores = pca.fit_transform(q4_scaled)

# 寄与率と累積寄与率を計算する
explained_variance = pca.explained_variance_
explained_variance_ratio = pca.explained_variance_ratio_
cumulative_explained_variance_ratio = np.cumsum(explained_variance_ratio)

# 結果テーブルを作成する
pca_summary = pd.DataFrame({
    '主成分': ['PC1', 'PC2'],
    '固有値': explained_variance,
    '寄与率': explained_variance_ratio,
    '累積寄与率': cumulative_explained_variance_ratio
})

# 表示用に丸める
pca_summary_display = pca_summary.copy()
pca_summary_display['固有値'] = pca_summary_display['固有値'].round(4)
pca_summary_display['寄与率'] = (pca_summary_display['寄与率'] * 100).round(2)
pca_summary_display['累積寄与率'] = (
    pca_summary_display['累積寄与率'] * 100).round(2)

# 主成分得点データを作成する
q4_pca_scores_df = pd.DataFrame({
    'PC1': q4_pca_scores[:, 0],
    'PC2': q4_pca_scores[:, 1]
})

# 主成分得点を保存する        # コメントアウト
# scores_output_path = '/mnt/data/q4_pca_2components_scores.csv'
# q4_pca_scores_df.to_csv(scores_output_path, index=False, encoding='utf-8-sig')

# 標準化済みデータも保存する  # コメントアウト
# scaled_output_path = '/mnt/data/q4_standardized_complete_data.csv'
# q4_scaled_df = pd.DataFrame(q4_scaled, columns=q4_columns)
# q4_scaled_df.to_csv(scaled_output_path, index=False, encoding='utf-8-sig')

# 結果を表示する
# caas_jupyter_tools.display_dataframe_to_user(   # コメントアウト
#     name='Q4 PCA 第2主成分までの寄与率',
#     dataframe=pca_summary_display
# )
print('Q4 PCA 第2主成分までの寄与率')               # 追加
display(pca_summary_display)                      # 追加

print('Q4完全データの標準化と主成分分析が完了しました。')
print(f'分析対象ケース数: {q4_data.shape[0]}')
print(f'分析対象変数数: {q4_data.shape[1]}')
print()
print('第2主成分までの結果')
for i in range(2):
    print(
        f"PC{i + 1}: 固有値={explained_variance[i]:.4f}, "
        f"寄与率={explained_variance_ratio[i] * 100:.2f}%, "
        f"累積寄与率={cumulative_explained_variance_ratio[i] * 100:.2f}%"
    )
print()
# print(f'主成分得点の保存先: {scores_output_path}')       # コメントアウト
# print(f'標準化済みデータの保存先: {scaled_output_path}')  $ コメントアウト

【実行結果】

⑥ 主成分分析:主成分負荷量

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
# import caas_jupyter_tools                                     # コメントアウト

# Q4完全データを読み込む
q4_complete_path = './data/ch9_add/q4_complete_data_no_missing.csv'  # 修正
q4_complete_df = pd.read_csv(q4_complete_path)

# Q4_1~Q4_19 の列を抽出する
q4_columns = [f'Q4_{i}' for i in range(1, 20)]
q4_columns = [col for col in q4_columns if col in q4_complete_df.columns]
q4_data = q4_complete_df[q4_columns].apply(pd.to_numeric)

# 標準化する
scaler = StandardScaler()
q4_scaled = scaler.fit_transform(q4_data)

# 主成分数2でPCAを実行する
pca = PCA(n_components=2)
pca_scores = pca.fit_transform(q4_scaled)

# 主成分負荷量を計算する
# 主成分負荷量 = 主成分係数 × sqrt(固有値)
loadings = pca.components_.T * np.sqrt(pca.explained_variance_)

# ★書籍の主成分負荷量は固有値の平方根で調整していない
# loadings = pca.components_.T

# Q4の質問内容をコードブックに基づいて対応づける
question_summary = {
    'Q4_1': '防衛力をもっと強化すべき',
    'Q4_2': '北朝鮮には対話より圧力を優先',
    'Q4_3': '非核三原則を堅持すべき',
    'Q4_4': '首相に靖国神社へ参拝してほしい',
    'Q4_5': '小さな政府の方が良い',
    'Q4_6': '公共事業による雇用確保は必要',
    'Q4_7': '財政再建より景気対策の財政出動',
    'Q4_8': '法人税率を引き上げるべき',
    'Q4_9': '農協の組織改革をさらに進めるべき',
    'Q4_10': '外国人労働者の受け入れを進めるべき',
    'Q4_11': '日本銀行は政策金利を引き上げるべき',
    'Q4_12': '治安のため権利制約は当然',
    'Q4_13': '公務員の人数や給与を抑制すべき',
    'Q4_14': '雇用の流動化を進めるべき',
    'Q4_15': '党内の派閥・政策集団は必要',
    'Q4_16': '被選挙権年齢を引き下げるべき',
    'Q4_17': '選択的夫婦別姓を認めるべき',
    'Q4_18': '同性婚を法律で認めるべき',
    'Q4_19': 'クオータ制を導入すべき'
}

# 一覧表を作成する
loading_table = pd.DataFrame({
    '項目番号': q4_columns,
    '質問内容(要約)': [question_summary[col] for col in q4_columns],
    'PC1負荷量': loadings[:, 0],
    'PC2負荷量': loadings[:, 1]
})

# 表示用に丸める
loading_table_display = loading_table.copy()
loading_table_display['PC1負荷量'] = loading_table_display['PC1負荷量'].round(3)
loading_table_display['PC2負荷量'] = loading_table_display['PC2負荷量'].round(3)

# # CSVとして保存する   # コメントアウト
# output_path = '/mnt/data/q4_pca_loadings_table.csv'
# loading_table_display.to_csv(output_path, index=False, encoding='utf-8-sig')

# # 表示する
# caas_jupyter_tools.display_dataframe_to_user(       # コメントアウト
#     name='Q4主成分負荷量一覧表',
#     dataframe=loading_table_display
# )
print('Q4主成分負荷量一覧表')
display(loading_table_display)

# print('Q4主成分負荷量一覧表を作成しました。')          # コメントアウト
# print(f'保存先: {output_path}')
print()
print('寄与率')
print(f'PC1: {pca.explained_variance_ratio_[0] * 100:.2f}%')
print(f'PC2: {pca.explained_variance_ratio_[1] * 100:.2f}%')
print(f'累積寄与率: {pca.explained_variance_ratio_[:2].sum() * 100:.2f}%')

【実行結果】

⑦ 主成分分析:候補者の主成分得点の散布図

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# 日本語フォントを設定する
# 利用可能なフォントから日本語表示に使えるものを探す
available_fonts = {f.name for f in font_manager.fontManager.ttflist}
candidate_fonts = [
    'Noto Sans CJK JP',
    'IPAexGothic',
    'IPAGothic',
    'TakaoGothic',
    'Yu Gothic',
    'MS Gothic'
]

selected_font = None
for font_name in candidate_fonts:
    if font_name in available_fonts:
        selected_font = font_name
        break

if selected_font is not None:
    plt.rcParams['font.family'] = selected_font

# マイナス記号が文字化けしないように設定する
plt.rcParams['axes.unicode_minus'] = False

# 元のCSVを読み込む
csv_path = './data/ch9_add//2024UTASP20241125.csv'   # 修正
df = pd.read_csv(csv_path, encoding='cp932')

# Q4_1~Q4_19 の列を定義する
q4_columns = [f'Q4_{i}' for i in range(1, 20)]

# Q4データを数値型として取り出す
q4_df = df[q4_columns].apply(pd.to_numeric, errors='coerce')

# 無回答を含むケースを除外する
# 欠損値 NaN と、コードブック上の無回答コード 99 を除く
complete_mask = q4_df.notna().all(axis=1) & (q4_df != 99).all(axis=1)

# 分析対象データを作成する
analysis_df = df.loc[complete_mask].copy()
q4_complete = q4_df.loc[complete_mask].copy()

# Q4完全データを標準化する
scaler = StandardScaler()
q4_scaled = scaler.fit_transform(q4_complete)

# 主成分分析を実行する
pca = PCA(n_components=2)
scores = pca.fit_transform(q4_scaled)

# 主成分得点を追加する
analysis_df['PC1'] = scores[:, 0]
analysis_df['PC2'] = scores[:, 1]

# 公認政党コードを日本語略称に変換する
party_abbrev_map = {
    1: '自民',
    2: '立民',
    3: '維新',
    4: '公明',
    5: '共産',
    6: '国民',
    7: 'れいわ',
    8: '社民',
    9: '参政',
    10: '保守',
    11: '諸派',
    12: '無所属'
}

# データ内に存在する政党コードを昇順に並べる
party_order = sorted(analysis_df['PARTY'].dropna().unique())
n_parties = len(party_order)

# 政党数に応じた色を重複なく用意する
cmap = plt.get_cmap('tab20', n_parties)

# 散布図を作成する
plt.figure(figsize=(10, 8))

for i, code in enumerate(party_order):
    temp = analysis_df[analysis_df['PARTY'] == code]
    plt.scatter(
        temp['PC1'],
        temp['PC2'],
        marker='o',
        s=36,
        alpha=0.85,
        label=party_abbrev_map.get(code, f'政党{code}'),
        color=cmap(i)
    )

# 補助線を入れる
plt.axhline(y=0, linewidth=1)
plt.axvline(x=0, linewidth=1)

# 軸ラベルとタイトルを日本語にする
plt.xlabel('第1主成分:保守-リベラル')
plt.ylabel('第2主成分:積極-緊縮財政')
plt.title('候補者の主成分得点散布図(公認政党別)')

# 凡例を表示する
plt.legend(title='公認政党', bbox_to_anchor=(1.02, 1), loc='upper left')

plt.tight_layout()
plt.show()

【実行結果】

⑧ 主成分分析:主成分得点の政党別平均・標準偏差

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
# import caas_jupyter_tools                          # コメントアウト

# 元データを読み込む
csv_path = './data/ch9_add/2024UTASP20241125.csv'
df = pd.read_csv(csv_path, encoding='cp932')

# Q4_1~Q4_19 を抽出する
q4_columns = [f'Q4_{i}' for i in range(1, 20)]
q4_df = df[q4_columns].apply(pd.to_numeric, errors='coerce')

# Q4完全データの条件:
# 欠損値なし、かつ 99(無回答)を含まない
complete_mask = q4_df.notna().all(axis=1) & (q4_df != 99).all(axis=1)

# 完全データを作成する
analysis_df = df.loc[complete_mask].copy()
q4_complete = q4_df.loc[complete_mask].copy()

# Q4完全データを標準化する
scaler = StandardScaler()
q4_scaled = scaler.fit_transform(q4_complete)

# 主成分分析を実行する
pca = PCA(n_components=2)
scores = pca.fit_transform(q4_scaled)

# 主成分得点を追加する
analysis_df['PC1_保守_リベラル'] = scores[:, 0]
analysis_df['PC2_積極_緊縮財政'] = scores[:, 1]

# 政党番号と政党略称
party_name_map = {
    1: '自民',
    2: '立民',
    3: '維新',
    4: '公明',
    5: '共産',
    6: '国民',
    7: 'れいわ',
    8: '社民',
    9: '参政',
    10: '保守',
    11: '諸派',
    12: '無所属'
}

# 諸派(11)と無所属(12)を除外する
party_analysis_df = analysis_df[~analysis_df['PARTY'].isin([11, 12])].copy()

# 政党ごとに主成分得点の平均値と標準偏差を計算する
party_features = (
    party_analysis_df
    .groupby('PARTY')
    .agg(
        政党内候補者数=('PARTY', 'size'),
        PC1平均=('PC1_保守_リベラル', 'mean'),
        PC1標準偏差=('PC1_保守_リベラル', 'std'),
        PC2平均=('PC2_積極_緊縮財政', 'mean'),
        PC2標準偏差=('PC2_積極_緊縮財政', 'std')
    )
    .reset_index()
    .sort_values('PARTY')
)

# 政党名を追加する
party_features.insert(
    1,
    '政党名',
    party_features['PARTY'].map(party_name_map)
)

# 列名を整理する
party_features = party_features.rename(columns={'PARTY': '政党番号'})

# 表示用に丸める
party_features_display = party_features.copy()
for col in ['PC1平均', 'PC1標準偏差', 'PC2平均', 'PC2標準偏差']:
    party_features_display[col] = party_features_display[col].round(3)

# CSVとして保存する
output_path = './data/ch9_add/q4_party_pca_features.csv'   # 修正
party_features_display.to_csv(output_path, index=False, encoding='utf-8-sig')

# # 表示する          # コメントアウト
# caas_jupyter_tools.display_dataframe_to_user(
#     name='政党別 主成分特徴量(諸派・無所属除外)',
#     dataframe=party_features_display
# )
print('政党別 主成分特徴量(諸派・無所属除外)')    # 追加
display(party_features_display)                 # 追加

print('政党別の主成分特徴量を作成しました。')
print(f'Q4完全データのケース数: {analysis_df.shape[0]}')
print(f'諸派・無所属を除外後のケース数: {party_analysis_df.shape[0]}')
print(f'集計対象政党数: {party_features_display.shape[0]}')
# print(f'保存先: {output_path}')                # コメントアウト

【実行結果】

⑨ k-means法:クラスターの表示

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
# import caas_jupyter_tools                                  # コメントアウト

# 政党別主成分特徴量を読み込む
features_path = './data/ch9_add/q4_party_pca_features.csv'   # 修正
party_features = pd.read_csv(features_path)

# k-means法に使う4つの特徴量
feature_columns = ['PC1平均', 'PC1標準偏差', 'PC2平均', 'PC2標準偏差']

# 特徴量を取り出す
X = party_features[feature_columns].copy()

# k-means法では尺度の違いが距離に影響するため、特徴量を標準化する
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# クラスター数3でk-means法を実行する
# random_state を固定して、同じ結果を再現できるようにする
kmeans = KMeans(n_clusters=3, random_state=42, n_init=20)
party_features['クラスター'] = kmeans.fit_predict(X_scaled) + 1

# クラスター番号を、PC1平均のクラスター中心が小さい順に並び替える
# これにより、クラスター番号の解釈を安定させる
cluster_centers_scaled = kmeans.cluster_centers_
cluster_centers_original = scaler.inverse_transform(cluster_centers_scaled)

cluster_order = (
    pd.DataFrame(cluster_centers_original, columns=feature_columns)
    .assign(元クラスター番号=range(1, 4))
    .sort_values('PC1平均')
    .reset_index(drop=True)
)

cluster_renumber_map = {
    old_cluster: new_cluster for new_cluster, old_cluster 
    in enumerate(cluster_order['元クラスター番号'], start=1)
}

party_features['クラスター'] = (
    party_features['クラスター'].map(cluster_renumber_map)
)

# 政党番号順に並べる
party_features = party_features.sort_values('政党番号').reset_index(drop=True)

# 表示用の一覧表
cluster_result = party_features[
    ['政党番号', '政党名', 'クラスター'] + feature_columns
].copy()

# 数値を見やすく丸める
for col in feature_columns:
    cluster_result[col] = cluster_result[col].round(3)

# クラスターごとの政党一覧を作成する
cluster_party_list = (
    cluster_result
    .groupby('クラスター')
    .agg(
        政党一覧=('政党名', lambda x: '、'.join(x)),
        政党数=('政党名', 'size')
    )
    .reset_index()
    .sort_values('クラスター')
)

# # CSVとして保存する   # コメントアウト
# result_path = '/mnt/data/q4_party_kmeans_3clusters.csv'
# cluster_result.to_csv(result_path, index=False, encoding='utf-8-sig')

# cluster_list_path = '/mnt/data/q4_party_kmeans_3clusters_party_list.csv'
# cluster_party_list.to_csv(
#     cluster_list_path, index=False, encoding='utf-8-sig'
# )

# # 表を表示する        # コメントアウト
# caas_jupyter_tools.display_dataframe_to_user(
#     name='k-means法による政党分類(3クラスター)',
#     dataframe=cluster_result
# )
print('k-means法による政党分類(3クラスター)')              # 追加
display(cluster_result)                                   # 追加

# caas_jupyter_tools.display_dataframe_to_user(           # コメントアウト
#     name='クラスターごとの政党一覧',
#     dataframe=cluster_party_list
# )
print('クラスターごとの政党一覧')                           # 追加
display(cluster_party_list)                               # 追加


print('k-means法による政党分類が完了しました。')
print(f'クラスター数: 3')
print(f'使用した特徴量: {feature_columns}')
# print(f'分類結果の保存先: {result_path}')                # コメントアウト
# print(f'クラスター別一覧の保存先: {cluster_list_path}')   # コメントアウト

【実行結果】

⑩ k-means法:可視化

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager
from sklearn.cluster import KMeans
# import caas_jupyter_tools              # コメントアウト

# 日本語フォントを設定する
available_fonts = {f.name for f in font_manager.fontManager.ttflist}
candidate_fonts = [
    'Noto Sans CJK JP',
    'IPAexGothic',
    'IPAGothic',
    'TakaoGothic',
    'Yu Gothic',
    'MS Gothic'
]

selected_font = None
for font_name in candidate_fonts:
    if font_name in available_fonts:
        selected_font = font_name
        break

if selected_font is not None:
    plt.rcParams['font.family'] = selected_font

plt.rcParams['axes.unicode_minus'] = False

# 政党別主成分特徴量を読み込む
features_path = './data/ch9_add/q4_party_pca_features.csv'   # 修正
party_features = pd.read_csv(features_path)

# k-means法に使う4つの特徴量
# 今回は標準化しない
feature_columns = ['PC1平均', 'PC1標準偏差', 'PC2平均', 'PC2標準偏差']
X = party_features[feature_columns].copy()

# クラスター数3でk-means法を実行する
kmeans = KMeans(n_clusters=3, random_state=42, n_init=50)
raw_cluster = kmeans.fit_predict(X) + 1

party_features['元クラスター'] = raw_cluster

# クラスター番号をPC1平均の中心が小さい順に並べ替える
# これにより、図上の左側からクラスター1, 2, 3の順に読みやすくする
cluster_centers = pd.DataFrame(kmeans.cluster_centers_, columns=feature_columns)
cluster_centers['元クラスター'] = range(1, 4)

cluster_order = (
    cluster_centers
    .sort_values('PC1平均')
    .reset_index(drop=True)
)

cluster_renumber_map = {
    old_cluster: new_cluster
    for new_cluster, old_cluster 
    in enumerate(cluster_order['元クラスター'], start=1)
}

party_features['クラスター'] = (
    party_features['元クラスター'].map(cluster_renumber_map)
)

# 政党番号順に整理する
party_features = party_features.sort_values('政党番号').reset_index(drop=True)

# 表示用の分類結果
cluster_result = party_features[
    ['政党番号', '政党名', 'クラスター'] + feature_columns
].copy()

for col in feature_columns:
    cluster_result[col] = cluster_result[col].round(3)

# クラスターごとの政党一覧
cluster_party_list = (
    cluster_result
    .groupby('クラスター')
    .agg(
        政党一覧=('政党名', lambda x: '、'.join(x)),
        政党数=('政党名', 'size')
    )
    .reset_index()
    .sort_values('クラスター')
)

# # 保存する   # コメントアウト
# cluster_result_path = '/mnt/data/q4_party_kmeans_3clusters_unscaled.csv'
# cluster_party_list_path = (
#     '/mnt/data/q4_party_kmeans_3clusters_unscaled_party_list.csv'
# )
# cluster_result.to_csv(cluster_result_path, index=False, encoding='utf-8-sig')
# cluster_party_list.to_csv(
#     cluster_party_list_path, index=False, encoding='utf-8-sig'
# )

# # 表示する   # コメントアウト
# caas_jupyter_tools.display_dataframe_to_user(
#     name='k-means法による政党分類(未標準化・3クラスター)',
#     dataframe=cluster_result
# )
print('k-means法による政党分類(未標準化・3クラスター)')
display(cluster_result)

# caas_jupyter_tools.display_dataframe_to_user(   # コメントアウト
#     name='クラスターごとの政党一覧(未標準化)',
#     dataframe=cluster_party_list
# )
print('クラスターごとの政党一覧(未標準化)')
display(cluster_party_list)

# 政党略称
party_abbrev_map = {
    1: '自民',
    2: '立民',
    3: '維新',
    4: '公明',
    5: '共産',
    6: '国民',
    7: 'れいわ',
    8: '社民',
    9: '参政',
    10: '保守'
}
party_features['略称'] = party_features['政党番号'].map(party_abbrev_map)

# クラスター色
cluster_order_plot = sorted(party_features['クラスター'].unique())
cmap = plt.get_cmap('tab10', len(cluster_order_plot))
cluster_color_map = {
    cluster: cmap(i)
    for i, cluster in enumerate(cluster_order_plot)
}

# 散布図を描画する
plt.figure(figsize=(10, 8))

# 平均値を丸印でプロットする
for cluster in cluster_order_plot:
    temp = party_features[party_features['クラスター'] == cluster]
    plt.scatter(
        temp['PC1平均'],
        temp['PC2平均'],
        marker='o',
        s=120,
        color=cluster_color_map[cluster],
        label=f'クラスター{cluster}',
        alpha=0.9
    )

# 各政党の標準偏差を1本の矢印で表す
# 始点:平均値 (PC1平均, PC2平均)
# 矢印ベクトル:(PC1標準偏差, PC2標準偏差)
for _, row in party_features.iterrows():
    x = row['PC1平均']
    y = row['PC2平均']
    dx = row['PC1標準偏差']
    dy = row['PC2標準偏差']
    color = cluster_color_map[row['クラスター']]

    plt.arrow(
        x, y, dx, dy,
        length_includes_head=True,
        head_width=0.10,
        head_length=0.14,
        linewidth=1.6,
        color=color,
        alpha=0.75
    )

    plt.text(
        x + 0.08,
        y + 0.08,
        row['略称'],
        fontsize=11,
        weight='bold'
    )

# 原点補助線
plt.axhline(y=0, linewidth=1, color='gray', alpha=0.7)
plt.axvline(x=0, linewidth=1, color='gray', alpha=0.7)

plt.xlabel('第1主成分平均:保守-リベラル')
plt.ylabel('第2主成分平均:積極-緊縮財政')
plt.title('政党別主成分特徴量の散布図(未標準化k-means・3クラスター)')
plt.legend(title='クラスター', loc='best')

plt.tight_layout()

# コメントアウト
# plot_path = '/mnt/data/q4_party_kmeans_unscaled_scatter_with_sd_vector.png'
# plt.savefig(plot_path, dpi=200, bbox_inches='tight')

plt.show()

print('未標準化の4特徴量でk-means法をやり直しました。')
print(f'使用した特徴量: {feature_columns}')
# print(f'分類結果の保存先: {cluster_result_path}')              # コメントアウト
# print(f'クラスター別一覧の保存先: {cluster_party_list_path}')
# print(f'散布図の保存先: {plot_path}')

【実行結果】

⑪ k-means法:シルエット分析

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, silhouette_samples
# import caas_jupyter_tools                            # コメントアウト

# 日本語フォントを設定する
available_fonts = {f.name for f in font_manager.fontManager.ttflist}
candidate_fonts = [
    'Noto Sans CJK JP',
    'IPAexGothic',
    'IPAGothic',
    'TakaoGothic',
    'Yu Gothic',
    'MS Gothic'
]

selected_font = None
for font_name in candidate_fonts:
    if font_name in available_fonts:
        selected_font = font_name
        break

if selected_font is not None:
    plt.rcParams['font.family'] = selected_font

plt.rcParams['axes.unicode_minus'] = False

# 政党別主成分特徴量を読み込む
features_path = './data/ch9_add/q4_party_pca_features.csv'   # 修正
party_features = pd.read_csv(features_path)

# シルエット分析に使う4つの特徴量
# 前回の修正版と合わせて、標準化しない
feature_columns = ['PC1平均', 'PC1標準偏差', 'PC2平均', 'PC2標準偏差']
X = party_features[feature_columns].copy()

# クラスター数2~6でシルエット分析を実行する
summary_rows = []
detail_rows = []

for k in range(2, 7):
    kmeans = KMeans(n_clusters=k, random_state=42, n_init=50)
    labels = kmeans.fit_predict(X)

    avg_silhouette = silhouette_score(X, labels)
    sample_silhouettes = silhouette_samples(X, labels)

    summary_rows.append({
        'クラスター数': k,
        '平均シルエット係数': avg_silhouette,
        '最小シルエット係数': sample_silhouettes.min(),
        '最大シルエット係数': sample_silhouettes.max()
    })

    for idx, row in party_features.iterrows():
        detail_rows.append({
            'クラスター数': k,
            '政党番号': row['政党番号'],
            '政党名': row['政党名'],
            'クラスター': labels[idx] + 1,
            'シルエット係数': sample_silhouettes[idx]
        })

silhouette_summary = pd.DataFrame(summary_rows)
silhouette_detail = pd.DataFrame(detail_rows)

# 表示用に丸める
silhouette_summary_display = silhouette_summary.copy()
for col in ['平均シルエット係数', '最小シルエット係数', '最大シルエット係数']:
    silhouette_summary_display[col] = silhouette_summary_display[col].round(3)

# # CSVとして保存する   # コメントアウト
# summary_path = '/mnt/data/q4_party_silhouette_summary_k2_k6.csv'
# detail_path = '/mnt/data/q4_party_silhouette_detail_k2_k6.csv'
# silhouette_summary_display.to_csv(
#     summary_path, index=False, encoding='utf-8-sig'
# )
# silhouette_detail.to_csv(detail_path, index=False, encoding='utf-8-sig')

# # 一覧表を表示する    # コメントアウト
# caas_jupyter_tools.display_dataframe_to_user(
#     name='シルエット分析結果(クラスター数2~6)',
#     dataframe=silhouette_summary_display
# )
print('シルエット分析結果(クラスター数2~6)')  # 追加
display(silhouette_summary_display)           # 追加

# 折れ線グラフを作成する
plt.figure(figsize=(8, 5))
plt.plot(
    silhouette_summary['クラスター数'],
    silhouette_summary['平均シルエット係数'],
    marker='o'
)
plt.xticks(range(2, 7))
plt.xlabel('クラスター数')
plt.ylabel('平均シルエット係数')
plt.title('シルエット分析:クラスター数2~6')
plt.grid(True, alpha=0.3)
plt.tight_layout()

# コメントアウト
# plot_path = '/mnt/data/q4_party_silhouette_k2_k6.png'
# plt.savefig(plot_path, dpi=200, bbox_inches='tight')

plt.show()

# 最良のクラスター数を確認する
best_row = silhouette_summary.loc[silhouette_summary['平均シルエット係数'].idxmax()]

print('シルエット分析が完了しました。')
print(f'使用した特徴量: {feature_columns}')
print('特徴量は標準化していません。')
print(f"平均シルエット係数が最大のクラスター数: {int(best_row['クラスター数'])}")
print(f"最大の平均シルエット係数: {best_row['平均シルエット係数']:.3f}")
# print(f'結果表の保存先: {summary_path}')   # コメントアウト
# print(f'詳細表の保存先: {detail_path}')
# print(f'グラフの保存先: {plot_path}')

【実行結果】

⑫ 追加分析:データ前処理

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# import caas_jupyter_tools                         # コメントアウト

# -----------------------------
# 0. 基本設定
# -----------------------------

# 日本語フォントを設定する
available_fonts = {f.name for f in font_manager.fontManager.ttflist}
candidate_fonts = [
    'Noto Sans CJK JP',
    'IPAexGothic',
    'IPAGothic',
    'TakaoGothic',
    'Yu Gothic',
    'MS Gothic'
]

selected_font = None
for font_name in candidate_fonts:
    if font_name in available_fonts:
        selected_font = font_name
        break

if selected_font is not None:
    plt.rcParams['font.family'] = selected_font

plt.rcParams['axes.unicode_minus'] = False

# 元データを読み込む
csv_path = './data/ch9_add/2024UTASP20241125.csv'   # 修正
df = pd.read_csv(csv_path, encoding='cp932')

# Q4列
q4_columns = [f'Q4_{i}' for i in range(1, 20)]

# Q4の質問文要約
q4_question_map = {
    'Q4_1': '防衛力強化',
    'Q4_2': '北朝鮮には対話より圧力',
    'Q4_3': '非核三原則の堅持',
    'Q4_4': '首相の靖国参拝',
    'Q4_5': '小さな政府',
    'Q4_6': '公共事業による雇用確保',
    'Q4_7': '財政出動による景気対策',
    'Q4_8': '法人税率引き上げ',
    'Q4_9': '農協改革',
    'Q4_10': '外国人労働者受け入れ',
    'Q4_11': '政策金利引き上げ',
    'Q4_12': '治安のための権利制約',
    'Q4_13': '公務員抑制',
    'Q4_14': '雇用流動化',
    'Q4_15': '派閥・政策集団の必要性',
    'Q4_16': '被選挙権年齢引き下げ',
    'Q4_17': '選択的夫婦別姓',
    'Q4_18': '同性婚',
    'Q4_19': 'クオータ制'
}

# 政党名
party_name_map = {
    1: '自民',
    2: '立民',
    3: '維新',
    4: '公明',
    5: '共産',
    6: '国民',
    7: 'れいわ',
    8: '社民',
    9: '参政',
    10: '保守',
    11: '諸派',
    12: '無所属'
}

# Q4を数値化
q4_df = df[q4_columns].apply(pd.to_numeric, errors='coerce')

# Q4完全データ:欠損なし、かつ99なし
complete_mask = q4_df.notna().all(axis=1) & (q4_df != 99).all(axis=1)

analysis_df = df.loc[complete_mask].copy()
q4_complete = q4_df.loc[complete_mask].copy()

# 諸派・無所属は政党単位分析から除外
party_mask = ~analysis_df['PARTY'].isin([11, 12])
party_df = analysis_df.loc[party_mask].copy()
q4_party = q4_complete.loc[party_mask].copy()

party_df['政党名'] = party_df['PARTY'].map(party_name_map)

# 念のためインデックスを揃える
party_df = party_df.reset_index(drop=True)
q4_party = q4_party.reset_index(drop=True)

print('前処理完了')
print(f'Q4完全データのケース数: {len(analysis_df)}')
print(f'諸派・無所属除外後のケース数: {len(party_df)}')
print(f'対象政党数: {party_df["PARTY"].nunique()}')

【実行結果】

⑬ 追加分析:1. 争点分断分析

# -----------------------------
# 1. 争点分断分析
# -----------------------------

# 政党別・Q4項目別平均
party_item_means = (
    pd.concat([party_df[['PARTY', '政党名']], q4_party], axis=1)
    .groupby(['PARTY', '政党名'])[q4_columns]
    .mean()
    .reset_index()
    .sort_values('PARTY')
)

# 項目ごとに、政党平均のばらつきを計算する
issue_rows = []

for col in q4_columns:
    means = party_item_means[col]
    issue_rows.append({
        '項目番号': col,
        '質問内容': q4_question_map[col],
        '政党平均の標準偏差': means.std(ddof=1),
        '政党平均の最小値': means.min(),
        '政党平均の最大値': means.max(),
        '政党平均の最大差': means.max() - means.min()
    })

issue_division = pd.DataFrame(issue_rows)
issue_division = (
    issue_division
    .sort_values('政党平均の標準偏差', ascending=False)
    .reset_index(drop=True)
)
issue_division.insert(0, '順位', range(1, len(issue_division) + 1))

# 表示用に丸める
issue_division_display = issue_division.copy()
for col in ['政党平均の標準偏差', '政党平均の最小値', '政党平均の最大値', 
            '政党平均の最大差']:
    issue_division_display[col] = issue_division_display[col].round(3)

# 保存   # コメントアウト
# issue_division_path = '/mnt/data/q4_issue_division_ranking.csv'
# issue_division_display.to_csv(
#     issue_division_path, index=False, encoding='utf-8-sig'
# )

# 表示   # コメントアウト
# caas_jupyter_tools.display_dataframe_to_user(
#     name='争点分断分析:政党差が大きいQ4項目ランキング',
#     dataframe=issue_division_display
# )
print('争点分断分析:政党差が大きいQ4項目ランキング')  # 追加
display(issue_division_display)                    # 追加


# 上位10項目の横棒グラフ
top10_issue = issue_division_display.head(10).sort_values('政党平均の標準偏差')

plt.figure(figsize=(9, 6))
plt.barh(top10_issue['質問内容'], top10_issue['政党平均の標準偏差'])
plt.xlabel('政党平均の標準偏差')
plt.ylabel('Q4項目')
plt.title('政党間で意見が分かれるQ4項目 上位10')
plt.tight_layout()

# コメントアウト
# issue_plot_path = '/mnt/data/q4_issue_division_top10.png'
# plt.savefig(issue_plot_path, dpi=200, bbox_inches='tight')

plt.show()

print('争点分断分析が完了しました。')
# print(f'結果表: {issue_division_path}') # コメントアウト
# print(f'グラフ: {issue_plot_path}')     # コメントアウト

【実行結果】

(注)「2. 政党プロフィール分析」と「3. 党内多様性分析」について、ChatGPTからのコード開示はありませんでした。

⑭ 追加分析:4. 候補者タイプ分析

# -----------------------------
# 4. 候補者タイプ分析
# -----------------------------

# 候補者1176人をQ4回答パターンだけで分類する
# Q4はすべて1~5の同じ尺度なので、ここでは標準化せずに使う
X_candidates = q4_complete.copy().reset_index(drop=True)
candidate_meta = analysis_df[['PARTY']].copy().reset_index(drop=True)
candidate_meta['政党名'] = candidate_meta['PARTY'].map(party_name_map)

# 諸派・無所属を含めて候補者タイプを作る
# タイプ数は、解釈しやすさを優先して5とする
k_candidate = 5
candidate_kmeans = KMeans(n_clusters=k_candidate, random_state=42, n_init=50)
candidate_labels = candidate_kmeans.fit_predict(X_candidates)

candidate_type_df = pd.concat([candidate_meta, X_candidates], axis=1)
candidate_type_df['候補者タイプ'] = candidate_labels + 1

# タイプ番号を「保守度が強い順」に並べ替えるため、
# 保守系項目の平均を使ってタイプ順を調整する
# 値が低いほど賛成寄りなので、保守系項目の平均が低いタイプを前に置く
conservative_items = ['Q4_1', 'Q4_2', 'Q4_4', 'Q4_12', 'Q4_13']
liberal_items = ['Q4_16', 'Q4_17', 'Q4_18', 'Q4_19']

type_means = candidate_type_df.groupby('候補者タイプ')[q4_columns].mean()

type_order_base = pd.DataFrame({
    '旧タイプ': type_means.index,
    '保守系項目平均': type_means[conservative_items].mean(axis=1),
    '社会リベラル項目平均': type_means[liberal_items].mean(axis=1)
})

# 保守系項目に賛成寄り、社会リベラル項目に反対寄りほど保守タイプとみなす
# 1~5尺度なので、保守系は低いほど保守、社会リベラルは高いほど保守
type_order_base['保守度指標'] = (
    -type_order_base['保守系項目平均'] + type_order_base['社会リベラル項目平均']
)

type_order = (
    type_order_base
    .sort_values('保守度指標', ascending=False)
    .reset_index(drop=True)
)

type_renumber_map = {
    old_type: new_type
    for new_type, old_type in enumerate(type_order['旧タイプ'], start=1)
}

candidate_type_df['候補者タイプ'] = (
    candidate_type_df['候補者タイプ'].map(type_renumber_map)
)

# 候補者タイプ別のQ4平均
candidate_type_means = (
    candidate_type_df
    .groupby('候補者タイプ')[q4_columns]
    .mean()
    .reset_index()
)

# タイプ別人数
candidate_type_counts = (
    candidate_type_df
    .groupby('候補者タイプ')
    .size()
    .reset_index(name='候補者数')
)

candidate_type_summary = candidate_type_counts.merge(
    candidate_type_means, on='候補者タイプ'
)

# タイプ解釈用に主要指標を追加
candidate_type_summary['防衛・秩序系平均'] = (
    candidate_type_summary[conservative_items].mean(axis=1)
)
candidate_type_summary['社会リベラル系平均'] = (
    candidate_type_summary[liberal_items].mean(axis=1)
)
candidate_type_summary['経済・財政系平均'] = candidate_type_summary[
    ['Q4_5', 'Q4_6', 'Q4_7', 'Q4_8', 'Q4_11', 'Q4_13', 'Q4_14']
].mean(axis=1)

# タイプ名を手作業で付けるため、特徴を見やすくする
type_summary_display = candidate_type_summary[
    ['候補者タイプ', '候補者数', '防衛・秩序系平均', '社会リベラル系平均',
     '経済・財政系平均']
].copy()

for col in ['防衛・秩序系平均', '社会リベラル系平均', '経済・財政系平均']:
    type_summary_display[col] = type_summary_display[col].round(3)

# 政党構成:候補者タイプ × 政党
type_party_counts = (
    candidate_type_df
    .groupby(['候補者タイプ', '政党名'])
    .size()
    .reset_index(name='人数')
)

type_party_pivot = type_party_counts.pivot_table(
    index='候補者タイプ',
    columns='政党名',
    values='人数',
    fill_value=0
).reset_index()

# タイプ別に構成比も作る
type_party_ratio = type_party_pivot.copy()
party_cols = [col for col in type_party_ratio.columns if col != '候補者タイプ']
type_party_ratio[party_cols] = (
    type_party_ratio[party_cols]
    .div(type_party_ratio[party_cols].sum(axis=1), axis=0) * 100
)
type_party_ratio[party_cols] = type_party_ratio[party_cols].round(1)

# 候補者タイプ別の特徴項目
# 各タイプ平均と全候補者平均の差が大きい項目を上位5つ抽出
all_candidate_mean = X_candidates.mean()
type_feature_rows = []

for _, row in candidate_type_means.iterrows():
    candidate_type = row['候補者タイプ']
    for col in q4_columns:
        diff = row[col] - all_candidate_mean[col]
        direction = '全体より賛成寄り' if diff < 0 else '全体より反対寄り'
        type_feature_rows.append({
            '候補者タイプ': candidate_type,
            '項目番号': col,
            '質問内容': q4_question_map[col],
            'タイプ平均': row[col],
            '平均との差': diff,
            '特徴の向き': direction,
            '差の絶対値': abs(diff)
        })

type_features_long = pd.DataFrame(type_feature_rows)
type_features_top5 = (
    type_features_long
    .sort_values(['候補者タイプ', '差の絶対値'], ascending=[True, False])
    .groupby('候補者タイプ')
    .head(5)
    .reset_index(drop=True)
)

# 候補者タイプのシルエット係数も参考として計算
candidate_silhouette = silhouette_score(
    X_candidates, candidate_type_df['候補者タイプ']
)

# # 保存  # コメントアウト
# type_summary_path = '/mnt/data/q4_candidate_type_summary.csv'
# type_party_counts_path = '/mnt/data/q4_candidate_type_party_counts.csv'
# type_party_ratio_path = '/mnt/data/q4_candidate_type_party_ratio.csv'
# type_features_path = '/mnt/data/q4_candidate_type_top_features.csv'

# type_summary_display.to_csv(
#     type_summary_path, index=False, encoding='utf-8-sig'
# )
# type_party_pivot.to_csv(
#     type_party_counts_path, index=False, encoding='utf-8-sig'
# )
# type_party_ratio.to_csv(
#     type_party_ratio_path, index=False, encoding='utf-8-sig'
# )

type_features_top5_display = type_features_top5.copy()
for col in ['タイプ平均', '平均との差', '差の絶対値']:
    type_features_top5_display[col] = type_features_top5_display[col].round(3)
# コメントアウト
# type_features_top5_display.to_csv(type_features_path, index=False, 
#                                   encoding='utf-8-sig')

# 表示   # コメントアウト
# caas_jupyter_tools.display_dataframe_to_user(
#     name='候補者タイプ分析:タイプ別サマリー',
#     dataframe=type_summary_display
# )
print('候補者タイプ分析:タイプ別サマリー')
display(type_summary_display)

# caas_jupyter_tools.display_dataframe_to_user(          # コメントアウト
#     name='候補者タイプ分析:タイプ別の政党構成(人数)',
#     dataframe=type_party_pivot
# )
print('候補者タイプ分析:タイプ別の政党構成(人数)')
display(type_party_pivot)

# caas_jupyter_tools.display_dataframe_to_user(          # コメントアウト
#     name='候補者タイプ分析:タイプを特徴づけるQ4項目 上位5',
#     dataframe=type_features_top5_display[
#         ['候補者タイプ', '項目番号', '質問内容', 'タイプ平均', '平均との差', 
#          '特徴の向き']
#     ]
# )
print('候補者タイプ分析:タイプを特徴づけるQ4項目 上位5')
display(
    type_features_top5_display[
        ['候補者タイプ', '項目番号', '質問内容', 'タイプ平均', '平均との差',
         '特徴の向き']
    ]
)

print('候補者タイプ分析が完了しました。')
print(f'候補者タイプ数: {k_candidate}')
print(f'参考:平均シルエット係数 = {candidate_silhouette:.3f}')
# コメントアウト
# print(f'タイプ別サマリー: {type_summary_path}') 
# print(f'タイプ別政党構成: {type_party_counts_path}')
# print(f'タイプ別政党構成比: {type_party_ratio_path}')
# print(f'タイプ別特徴項目: {type_features_path}')

【実行結果】

シリーズの記事


次の記事

前の記事

目次


ブログの紹介


note で8つのシリーズ記事を書いています。
ぜひ覗いていってくださいね!

1.のんびり統計

統計検定2級の問題集を手がかりにして、確率・統計をざっくり掘り下げるブログです。
雑談感覚で大丈夫です。ぜひ覗いていってくださいね。
統計検定2級公式問題集CBT対応版に対応しています。
Python、EXCELのサンプルコードの配布もあります。

2.統計・データ分析とつながる

シリーズ「統計・データ分析とつながる」は、統計・データ分析との「つながり」を発掘して、コラム風に仕立てたブログシリーズです。
生成 AI の力を借りながら、統計・データ分析の入り口をイメージして、自由気ままに書きました。
たとえば…
・日常生活と統計のつながり
・統計検定2級からその先へのつながり
気楽にお読みいただけたら嬉しいです🍀

3.実験!たのしいベイズモデリング1&2をPyMC Ver.5で

書籍「たのしいベイズモデリング」・「たのしいベイズモデリング2」の心理学研究に用いられたベイズモデルを PyMC Ver.5で描いて分析します。
この書籍をはじめ、多くのベイズモデルは R言語+Stanで書かれています。
PyMCの可能性を探り出し、手軽にベイズモデリングを実践できるように努めます。
身近なテーマ、イメージしやすいテーマですので、ぜひぜひPyMCで動かして、一緒に楽しみましょう!

4.実験!岩波データサイエンス1のベイズモデリングをPyMC Ver.5で

書籍「実験!岩波データサイエンスvol.1」の4人のベイジアンによるベイズモデルを PyMC Ver.5で描いて分析します。
この書籍はベイズプログラミングのイロハをざっくりと学ぶことができる良書です。
楽しくPyMCモデルを動かして、ベイズと仲良しになれた気がします。
みなさんもぜひぜひPyMCで動かして、一緒に遊んで学びましょう!

5.楽しい写経 ベイズ・Python等

ベイズ、Python、その他の「書籍の写経活動」の成果をブログにします。
主にPythonへの翻訳に取り組んでいます。
写経に取り組むお仲間さんのサンプルコードになれば幸いです🍀

6.RとStanではじめる心理学のための時系列分析入門 を PythonとPyMC Ver.5 で

書籍「RとStanではじめる心理学のための時系列分析入門」の時系列分析をPythonとPyMC Ver.5 で実践します。
この書籍には時系列分析のテーマが盛りだくさん!
時系列分析の懐の深さを実感いたしました。
大好きなPythonで楽しく時系列分析を学びます。

7.データサイエンスっぽいことを綴る

統計、データ分析、AI、機械学習、Pythonのコラムを不定期に綴っています。
統計・データサイエンス書籍にまつわる記事が多いです。
「統計」「Python」「数学とPython」「R」のシリーズが生まれています。

8.Python機械学習プログラミング実践記

書籍「Python機械学習プログラミング PyTorch & scikit-learn編」を学んだときのさまざまな思いを記事にしました。
この書籍は、scikit-learnとPyTorchの教科書です。
よかったらぜひ、お試しくださいませ。

最後までお読みいただきまして、ありがとうございました。

いいなと思ったら応援しよう!

ネイピア DS 応援ありがとうございます。これからもがんばって記事を作成します!

この記事が参加している募集