このレクチャーでは、2012年のアメリカ大統領選挙について扱います。その内容にあまり詳しくない方は、以下が参考になると思います。 https://ja.wikipedia.org/wiki/2012%E5%B9%B4%E3%82%A2%E3%83%A1%E3%83%AA%E3%82%AB%E5%90%88%E8%A1%86%E5%9B%BD%E5%A4%A7%E7%B5%B1%E9%A0%98%E9%81%B8%E6%8C%99
基本的には民主党のオバマ候補と、共和党のロムニー候補の争いで、オバマ候補が勝利しました。
最初は、世論調査結果のデータを扱います。以下のような問題を設定してみましょう。
1.) どのような人達が調査対象だったか?
2.) 調査結果は、どちらの候補の有利を示しているか?
3.) 態度未定の人達が世論調査に与えた影響は?
4.) また、態度未定の人たちの動向は?
5.) 投票者の気持ちは、時間ともにどう変化したか?
6.) 討論会の影響を世論調査の結果から読み取ることができるか?
2つ目のデータセットについては、後半で。
まずはいつものように、必要なものをインポートします
import pandas as pd
from pandas import Series,DataFrame
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
%matplotlib inline
# Python2をお使いの場合は
from __future__ import division
データは、HuffPost Pollsterから持ってきます。サイトはこちら. 米国の選挙のデータですが、このコースが終わったあとに、このサイトの別のデータを解析するのにチャレンジしてみるのもよいかもしれません。
Webからデータととってくるのに便利な、requestsというモジュールを使います。インストールされていない場合は、こちらを参照してください。
CSV形式のテキストデータをファイルのように扱うために、StringIOを利用します。StringIOの詳しい解説(英語)
# Webからデータを取得するために、requestsをインポートします。
import requests
# CSVデータのために、StringIOをつかいます。
from io import StringIO
# Python2を使っているときは,以下のコードでimportします。
# from StringIO import StringIO
# データのURLです。
url = "http://elections.huffingtonpost.com/pollster/2012-general-election-romney-vs-obama.csv"
# requestsをつかってデータをtextとして取得します。
source = requests.get(url).text
# StringIOを使ってpandasのエラーを防ぎます。
poll_data = StringIO(source)
早速、DataFrameにしましょう。
poll_df = pd.read_csv(poll_data)
# データの概要です。
poll_df.info()
最初の5行を表示します。
poll_df.head()
支持政党で分類します。アメリカは2大政党制で、大きく共和党(the Republican party)と民主党(the Democratic party)に分けられます。
# ちょっと分かりにくいので、世論調査の主体とその支持政党をまとめて見ます。
poll_df[['Pollster','Partisan','Affiliation']].sort('Pollster').drop_duplicates()
# affiliationで数を分類します。
sns.countplot('Affiliation',data=poll_df)
概ね中立のように見えますが、民主党寄りの調査主体が多いようにも見えます。このことは、頭に入れておいてもいいかもしれません。調査対象の人々の属性で層別化してみましょう。
# 調査主体の支持政党を、調査対象で層別化します。
sns.countplot('Affiliation',data=poll_df,hue='Population', order=['Rep','Dem','None'])
概ね、選挙の投票に関連のある人々を対象にしているようですので、調査結果は信頼できそうです。 別の角度から解析を進めてみましょう。
poll_df.head()
オバマ、ロムニー、未定の3つの選択肢について、それぞれ平均的な支持率を計算してみます。
# 平均をとると、数値の列だけが残るので、いらないNumber of Observationsを削除します。
avg = pd.DataFrame(poll_df.mean())
avg.drop('Number of Observations',axis=0,inplace=True)
# 同じように、標準偏差を計算します。
std = pd.DataFrame(poll_df.std())
std.drop('Number of Observations',axis=0,inplace=True)
# pandas標準のplotで描画します。エラーバーも付けておきましょう。
avg.plot(yerr=std,kind='bar',legend=False)
未定の動向に注目しつつ、もう少し詳しく見ていくことにしましょう。
# 平均と標準偏差のDataFrameを連結します。
poll_avg = pd.concat([avg,std],axis=1)
# 名前を変えておきます。
poll_avg.columns = ['Average','STD']
poll_avg
非常に接戦の選挙戦に見えます。ただ、未定の人達は、いざ投票が始まれば、どちらかに投票することになるので、その動向が注目されます。ここはひとまず、未定の人達が、半分ずつ、両候補へ分かれると仮定して、選挙戦の最終的な結果を推定してみましょう。
poll_df.head()
両候補の支持率と未定の割合を、手っ取り早くプロットしてみます。
Note: 時間が逆順になっているのに注意してください。また同じEnd Dateに複数のプロトがある場合があります。
# 時間事に、支持率をプロットします。
poll_df.plot(x='End Date',y=['Obama','Romney','Undecided'],marker='o',linestyle='')
ちょっと見にくいので、両陣営の支持率の差を可視化するには、新しいデータを作って解析した方が良さそうです。
これは、前に株価のデータを解析したときのように、時系列のデータとして解析するのがよさそうです。 時間とともに変化する支持率を、datetimeモジュールを使って、時系列のデータとして扱ってみましょう。
from datetime import datetime
オバマとロムニーの支持率の差を計算し、新しい列に保存します。
# Differenceは「差」を意味する英単語です。
poll_df['Difference'] = (poll_df.Obama - poll_df.Romney)/100
poll_df.head()
Difference列は、「Obama - Romney」です。正の数は、オバマのリードを意味します。
これを使って、支持率の差が時間とともに、どう変化したかを計算します。同じ期間に行われた調査もあるので、groupbyを使って、データを整理しましょう。
# as_index=Flaseにすると、0,1,2,... のindexを維持します。
poll_df = poll_df.groupby(['Start Date'],as_index=False).mean()
poll_df.head()
時間とともに、支持率がどう変化したかを端的に可視化出来ます。
fig = poll_df.plot('Start Date','Difference',figsize=(12,4),marker='o',linestyle='-',color='purple')
候補者同士の討論会があった日付を、このプロットに描き込むと面白いかも知れません。
2012の討論会があったのは、10/3、10/11、10/22です。これらを描き込んでみましょう。
2012年の10月が、X軸上のいくつ目のデータなのかを知る必要があります。 ここは単純に、2012/10のデータを見て、どのindexをとればいいか確認することにしましょう。
poll_df[poll_df['Start Date'].apply(lambda x:x.startswith('2012-10'))]
3日はデータが無いので、2日にします。ですので、330、337、347です。
# まずは10月に限定したプロットです。
fig = poll_df.plot('Start Date','Difference',figsize=(12,4),marker='o',linestyle='-',color='purple',xlim=(329,356))
# 討論会の日程を描き込みます。
plt.axvline(x=330, linewidth=4, color='grey')
plt.axvline(x=337, linewidth=4, color='grey')
plt.axvline(x=347, linewidth=4, color='grey')
米国大統領選挙に馴染みが薄いので、データの解釈は難しいですが、このような解析が役に立つのはおわかりいただけるかと思います。
こうしたデータを解釈する際は、様々な要因に注意を払う必要もあります。例えば、これらの世論調査が全米のどの場所で行われたかなども、世論調査の結果に大きく影響します。
話題を変えて、両陣営への寄付に関するデータを分析していくことにします。
これまでで一番大きなデータセット(約150MB)になります。ここからダウンロード出来ます , Notebookが起動しているフォルダと同じ場所に保存しておきましょう。
このデータは、次の視点から分析を進めることにします。
1.) 寄付の金額とその平均的な額
2.) 候補者ごとの寄付の違い
3.) 民主党と共和党での寄付の違い
4.) 寄付した人々の属性について
5.) 寄付の総額になんらかのパターンがあるか?
# CSV形式のデータを読み込んで、DataFrameを作ります。
donor_df = pd.read_csv('Election_Donor_Data.csv')
# データの概要です。
donor_df.info()
# 最初の5行を見てみます。
donor_df.head()
まずは、寄付の総額と平均的な額を計算してみることにしましょう。
# 寄付の額をざっと眺めてみます。
donor_df['contb_receipt_amt'].value_counts()
#donor_df['contb_receipt_amt'].value_counts().shape
8079種類もあるのにはちょっと驚きです。代表値を計算してみましょう。
# 寄付の平均的な額
don_mean = donor_df['contb_receipt_amt'].mean()
# 標準偏差
don_std = donor_df['contb_receipt_amt'].std()
print('寄付の平均は{:0.2f}ドルで、その標準偏差は{:0.2f}です。'.format(don_mean,don_std))
ものすごく大きな標準偏差です。 非常に大きな値があるのでしょうか?分布の形がかなり偏っていることが予想されます。
# DataFrameの1列から、Seriesを作ります。
top_donor = donor_df['contb_receipt_amt'].copy()
# ソートしましょう。
top_donor.sort()
top_donor
負の数や、非常に大きな値が見えます。 負の数は、払い戻しのデータなどですので、ひとまず正の数だけに注目することにしましょう。
# 負の数を取り除きます。
top_donor = top_donor[top_donor >0]
# ソートします
top_donor.sort()
# よく寄付される額Top10をみてみましょう。
top_donor.value_counts().head(10)
Top10でも、10ドルから2,500ドルまで幅広いことがわかります。
寄付の額は、10や50などキリの良い数字が多いのかどう書きになります。 ヒストグラムを描いて、2,500ドルまでのデータを調べてみましょう。
# 2,500ドルまでの寄付のデータを取り出します。
com_don = top_donor[top_donor < 2500]
# binを細かくして、キリのいい数字にピークがあるか、見てみましょう。
com_don.hist(bins=100)
どうやら、我々の直感は正しかったようです。
政党ごとに寄付の額をまとめて見ることにします。 これをするには、まず候補者のデータに注目して、候補者の所属政党でデータを分類する事を試みます。
# 重複の無い候補者のデータを作って置きます。
candidates = donor_df.cand_nm.unique()
candidates
新たに、Party列を作ります。候補者の所属政党のデータを保持する辞書を作って、一気にDataFrameを更新しましょう。 (詳しくはLecture 36にあります)
# 所属政党の辞書です。
party_map = {'Bachmann, Michelle': 'Republican',
'Cain, Herman': 'Republican',
'Gingrich, Newt': 'Republican',
'Huntsman, Jon': 'Republican',
'Johnson, Gary Earl': 'Republican',
'McCotter, Thaddeus G': 'Republican',
'Obama, Barack': 'Democrat',
'Paul, Ron': 'Republican',
'Pawlenty, Timothy': 'Republican',
'Perry, Rick': 'Republican',
"Roemer, Charles E. 'Buddy' III": 'Republican',
'Romney, Mitt': 'Republican',
'Santorum, Rick': 'Republican'}
# 以下のコードで、DataFrame全体を更新できます。
donor_df['Party'] = donor_df.cand_nm.map(party_map)
もちろん、for文を使って同じ操作をすることが可能ですが、mapを使う方が早く終わります。
'''
for i in xrange(0,len(donor_df)):
if donor_df['cand_nm'][i] == 'Obama,Barack':
donor_df['Party'][i] = 'Democrat'
else:
donor_df['Party'][i] = 'Republican'
'''
払い戻しのデータを除去しておきましょう。完成したデータは次のような感じです。
# 払い戻しの除去
donor_df = donor_df[donor_df.contb_receipt_amt >0]
donor_df.head()
候補者ごとに寄付の額をまとめてみます。 まずはじめに、寄付の件数から。
# 候補者の名前でグループ化したあと、それぞれの寄付件数を表示します。
donor_df.groupby('cand_nm')['contb_receipt_amt'].count()
オバマの圧勝です。それもそのはず。彼は民主党からの唯一の候補者なので、寄付が集まっているわけです。寄付の額を見てみましょう。
# グループ化したあと、今度は寄付の総額を表示します。
donor_df.groupby('cand_nm')['contb_receipt_amt'].sum()
ちょっと読みにくいので、少し結果を整形することにします。
# データの準備です。
cand_amount = donor_df.groupby('cand_nm')['contb_receipt_amt'].sum()
# indexアクセスのための変数を用意します。
i = 0
for don in cand_amount:
print("{}は、{:.0f}ドル集めました。\n".format(cand_amount.index[i],don))
i += 1
文字での表示より、やはりグラフ表示がいいかもしれません。
# 棒グラフを描いてみましょう。
cand_amount.plot(kind='bar')
比較が簡単になりました。 オバマ候補だけで100億円以上のお金が寄付で集まっているのがわかります。 今度は、政党別で比較してみましょう。
# 政党ごとの寄付の額です。
donor_df.groupby('Party')['contb_receipt_amt'].sum().plot(kind='bar')
民主党(Democrat)はオバマ候補1人ですので、政党の比較では、共和党優勢ですが、共和党候補者達は、この額をみんなで分けているので、個人でみると厳しい情勢です。
最後に、寄付した人達の職業をまとめてみましょう。 まず、職業のデータを元のDataFrameから抜き出して、これをもとに、ピボットテーブルを作ります。このとき、職業ごとに民主党と共和党への寄付額が分かるようにします。 最後にこれらのデータをまとめて、寄付者の属性ごとの寄付額を算出してみましょう。
# 職業ごとに、政党別に分けて寄付額をまとめます。
occupation_df = donor_df.pivot_table('contb_receipt_amt',
index='contbr_occupation',
columns='Party', aggfunc='sum')
occupation_df.head(10)
DataFrameのサイズを見ておきましょう。
occupation_df.shape
4万5千以上の職種があるようで、簡単には描画できそうにありません。 閾値を決めて、寄付の総額が小さい職種については、表示を省略するようにしてみます。
# 百万ドル(約1億2千万)をひとつの区切りにしてみます。
occupation_df = occupation_df[occupation_df.sum(1) > 1000000]
# サイズを見てみましょう。
occupation_df.shape
これなら、グラフにできそうです。
# pandasの機能を使って、棒グラフを描いてみます。
occupation_df.plot(kind='bar')
kind = 'barh' (horizontal)とすると、横向きになります。
# 横向き(水平:Horizontal)な図が描けました。
occupation_df.plot(kind='barh',figsize=(10,12),cmap='seismic')
occupation_df
色々と粗が目立ちます。ひとまず、「Information Requested」を取り除いて、「CEO」と「C.E.O.」を1つにまとめてみます。
# 無効な回答を取り除きます。
occupation_df.drop(['INFORMATION REQUESTED PER BEST EFFORTS','INFORMATION REQUESTED'],axis=0,inplace=True)
CEOとC.E.Oをまとめましょう。
# CEOにまとめます。
occupation_df.loc['CEO'] = occupation_df.loc['CEO'] + occupation_df.loc['C.E.O.']
# C.E.O.を消しましょう。
occupation_df.drop('C.E.O.',inplace=True)
再び、プロットします。
occupation_df.plot(kind='barh',figsize=(10,12),cmap='seismic')
CEO(企業経営者)は、保守的な思想の持ち主のように見えます。両党の税に対する考え方が反映されているのかも知れません。
選挙に関するデータを一通り解析しました。大きなデータセットなので、工夫次第でまだまだやれることがあると思いますので、是非チャレンジしてみてください。
日本でも、選挙に関するデータ解析の事例があります。是非、参考にしてみてください。 http://event.yahoo.co.jp/bigdata/senkyo201307/