Pythonでデータ分析実践演習 ~2016 New Coder Survey 編~

# はじめに

python及びnumpy, pandas, seaborn といったライブラリを使ってデータを扱う練習をします。
データは[kaggle](https://www.kaggle.com/)のデータを使います。
今回は[2016 New Coder Survey](https://www.kaggle.com/freecodecamp/2016-new-coder-survey-)のデータを使います。
データの内容は以下のようなものです。簡単にいうと、どんな人がコーディングの学習しているかのデータです。

>
Free Code Camp is an open source community where you learn to code and build projects for nonprofits.
>
CodeNewbie.org is the most supportive community of people learning to code.
>
Together, we surveyed more than 15,000 people who are actively learning to code. We reached them through the twitter accounts and email lists of various organizations that help people learn to code.
>
Our goal was to understand these people's motivations in learning to code, how they're learning to code, their demographics, and their socioeconomic background.


また、前提としてipython notebook上で実行を行っています。
バージョンは
pyenv: anaconda3-2.4.0 (Python 3.5.2 :: Anaconda 2.4.0)
です。

この分野に精通している方は内容について温かい目で見て頂き何かお気づきの点がありましたらアドバイス頂けますと幸いです。
「このデータを使って自分だったらこんな分析をする」 といった内容をコメント頂いても嬉しいです！（コードベースで頂けますと参考になります！）


# ライブラリのimport

使いそうなものは一旦読み込んでおきます。

```py3
import numpy as np
from numpy.random import randn
import pandas as pd
from pandas import Series, DataFrame
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
```

# データの読み込み

[2016 New Coder Survey](https://www.kaggle.com/freecodecamp/2016-new-coder-survey-)からデータをダウンロードして「code_survey.csv」という名前で同じフォルダに入れました。

```py3
survey_df = pd.read_csv('corder_survey.csv')
```

# データの概観

## shape

```py3
survey_df.shape
```

```
(15620, 113)
```

なるほど。結構項目ありますね。行の数が15620(データの対象人数)、列の数(回答項目)が113。

## info
infoも使うといいかもしれません。

```py3
survey_df.info()
```

```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 15620 entries, 0 to 15619
Columns: 113 entries, Age to StudentDebtOwe
dtypes: float64(85), object(28)
memory usage: 13.5+ MB
```

## describe

```py3
survey_df.describe()
```

各列の'count', 'mean', 'std', 'min', '25%', '50%', '75%', 'max'といった情報が分かります。
データが多すぎるのでデータは省略。

## カラムチェック

```
for col in survey_df.columns:
    print(col)
```

これで113項目を全て表示してみました。今回は練習なので使うカラムをはじめにピックアップしておこうと思います。

```
Gender: 性別
HasChildren: 子供の有無
EmploymentStatus: 現状の雇用形態
Age: 年齢
Income: 収入
HoursLearning: 学習時間
SchoolMajor: 専攻
```

# 各項目の概観

## Gender

### countplot

性別のデータから見てみます。ヒストグラムを作ってみます。
seabornのcountplotが便利です。

```py3
sns.countplot('Gender', data=survey_df)
```

![gender.png](https://qiita-image-store.s3.amazonaws.com/0/40796/02cd8657-7d9c-b884-a537-a0acae9ff7d9.png)

日本なら男女で分けてしまいそうですが、多様性があり海外っぽいですね。

[seaborn.countplot](https://stanford.edu/~mwaskom/software/seaborn/generated/seaborn.countplot.html)



ちなみに、単純なヒストグラムならmatplotlibで`plt.hist`とかあります。
（棒グラフを作る`plt.bar`もありますが、データの頻度分布からヒストグラムを作るときは`plt.hist`が楽です。

```py3
dataset = randn(100)
plt.hist(dataset)
```

（randnは正規分布に従った乱数を生成してくれます）

![plt.png](https://qiita-image-store.s3.amazonaws.com/0/40796/862aa0f1-e8a5-e4c7-ef48-6065eb678cb9.png)


色々なオプションもあります。

```py3
# normed: 正規化, alpha: 透明度, color: 色, bins: ビン数
plt.hist(dataset, normed=True, alpha=0.8, color='indianred', bins=15)
```

[matplotlib.pyplot.hist](http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.hist)

## HasChildren

同様に子供の有無も`countplot`を使って描画してみます。

```py3
sns.countplot('HasChildren', data=survey_df)
```

![has_children.png](https://qiita-image-store.s3.amazonaws.com/0/40796/0550f06c-622c-6dec-5f14-f8b11aeee19e.png)

0か1だと分かりにくいので子供なしを`No`、子供ありを`Yes`としてみます。

```py3
survey_df['HasChildren'].loc[survey_df['HasChildren'] == 0] = 'No'
survey_df['HasChildren'].loc[survey_df['HasChildren'] == 1] = 'Yes'
```

[pandas.DataFrame.loc](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.loc.html)

これで変換出来ました。

### df.map

mapを使った変換でも良さそうです。

```py3
survey_df['HasChildren'] = survey_df['HasChildren'].map({0: 'No', 1: 'Yes'})
sns.countplot('HasChildren', data=survey_df)
```

```py3
sns.countplot('HasChildren', data=survey_df)
```

![has_children_2.png](https://qiita-image-store.s3.amazonaws.com/0/40796/e8737a24-0521-f7b5-dc62-a5a3318357d7.png)

少し分かりやすいですね！

## EmploymentStatus

現状の雇用形態も`countplot`で表してみます。

```py3
sns.countplot('EmploymentStatus', data=survey_df)
```

![employment_status.png](https://qiita-image-store.s3.amazonaws.com/0/40796/e3ec44b3-32fe-05ba-e629-513c2188b058.png)

なんかごちゃっとして分かりにくい。。

というわけで軸を変えてみます。

### countplot 軸の変更

```py3
sns.countplot(y='EmploymentStatus', data=survey_df)
```

![employment_status_2.png](https://qiita-image-store.s3.amazonaws.com/0/40796/da402440-8de3-bf11-4793-3ddb4d878329.png)

見やすい！

## Age

これも`countplot`を使ってみます。

```py3
sns.countplot('Age', data=survey_df)
```

![age.png](https://qiita-image-store.s3.amazonaws.com/0/40796/a4793b83-489a-3785-38e0-358690479699.png)

カラフルで綺麗ですけど、グラフとしては見にくいですね。

というわけでグラフを滑らかにしてみます。

### kde plot

カーネル密度推定 ( kde: kernel density plot ) を使います。やり方自体は簡単です。

```py3
sns.kdeplot(survey_df['Age'])
```

![age_2.png](https://qiita-image-store.s3.amazonaws.com/0/40796/9fda85a8-c65d-ff1b-b1f6-bf8d3e3a0192.png)

20代から30代の方が多いんですね。想像通りといったところでしょうか。
ただ、ある程度年齢が上がっても裾は広がっていますね。

### カーネル密度推定

ここで、カーネル密度推定を少し考えてみます。（Wikipediaやその他サイトを見るとちゃんとした解説が見れるかと思います。）
[カーネル密度推定](https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E5%AF%86%E5%BA%A6%E6%8E%A8%E5%AE%9A) 

```py3
dataset = randn(30)
plt.hist(dataset, alpha=0.5)
sns.rugplot(dataset)
```

rugplotは各標本点を棒で示してくれます。

![kernel.png](https://qiita-image-store.s3.amazonaws.com/0/40796/772fbe77-ea26-cf3e-e230-52eaeb62306d.png)

このグラフの各標本点それぞれに対して、カーネル関数（一例として正規分布を考えると分かりやすい）を作ってそれを足し合わせるイメージです。

```py3
sns.kdeplot(dataset)
```

![kernel_2.png](https://qiita-image-store.s3.amazonaws.com/0/40796/9be82cfd-8421-5fd2-d38c-372677fb9754.png)

カーネル密度推定をする上で

- カーネル関数: 各標本点の影響度の広がり方
- バンド幅: カーネル関数の広がりの幅

の2つを決定する必要があります。

#### カーネル関数

色々なカーネル関数を用いることも出来ます。
デフォルトは gau （ガウス分布、正規分布）です。

```py3
kernel_options = ["gau", "biw", "cos", "epa", "tri", "triw"]
for kernel in kernel_options:
    sns.kdeplot(dataset, kernel=kernel, label=kernel)
```

![kernel_3.png](https://qiita-image-store.s3.amazonaws.com/0/40796/ebe43864-87a8-98a5-d2ec-b1f385e7ab26.png)



#### バンド幅

バンド幅も代えられます。

```py3
for bw in np.arange(0.5, 2, 0.25):
  sns.kdeplot(dataset, bw=bw, label=bw)
```

![kernel_4.png](https://qiita-image-store.s3.amazonaws.com/0/40796/d3252bae-f8ba-d15c-f1a8-e3acb8b6abf2.png)

ここまででカーネル密度推定の説明は一旦区切り、次にいきます。

## Income

ここでも`kdeplot`を使います。

```py3
sns.kdeplot(survey_df['Income'])
```

![income.png](https://qiita-image-store.s3.amazonaws.com/0/40796/c4c278a3-4c86-96e7-2b32-71684ac849db.png)

単位はドルでしょうから年収ですね。

データをもう少し見てみます。

### describe

```py3
survey_df['Income'].describe()
```

```
RuntimeWarning: Invalid value encountered in median
count      7329.000000
mean      44930.010506
std       35582.783216
min        6000.000000
25%                NaN
50%                NaN
75%                NaN
max      200000.000000
Name: Income, dtype: float64
```

四分位点がNaNになる問題は記事作成時点で既に解決済みではあるようですが、merge待ちみたいです。
気にせずバージョンアップを待ちます。
[describe() returns RuntimeWarning: Invalid value encountered in median RuntimeWarning #13146](https://github.com/pydata/pandas/issues/13146)

### boxplot

boxplot(boxplot)を作成したいと思います。

```py3
sns.boxplot(survey['Income'])
```

![boxplot.png](https://qiita-image-store.s3.amazonaws.com/0/40796/06e1e68f-8a0f-fbf7-e9ba-ebb2c48a6892.png)

左の縦線から順に箱ひげの最小、第一四分位点(Q1)、中央値、第三四分位点（Q3)、箱ひげの最大を表します。IQR = Q3-Q1 として (最小値 - IQR1.5) ~ (最大値 + IQR1.5 ) から外れたものを箱ひげから外れた外れ値として黒の点で表されます。

外れ値をなしで表現することも出来ます。

```py3
sns.boxplot(survey['Income'], whips=np.inf)
```

![boxplot_2.png](https://qiita-image-store.s3.amazonaws.com/0/40796/b1281b92-8bac-99f1-d769-ef8649951169.png)

### violinplot

boxplotにkdeの情報を持たせたヴァヴィオリンプロットというものもあります。

```py3
sns.violinplot(survey_df['Income'])
```

![violinplot.png](https://qiita-image-store.s3.amazonaws.com/0/40796/b3819ae9-a0c8-cddc-e2b1-c537ef7b2b18.png)

分布がより分かりやすいですね！

## HoursLearning

学習時間を見てみます。
まずはkdeプロットをしてみます。

```py3
sns.kdeplot(survey_df['HoursLearning'])
```

![hours_kde.png](https://qiita-image-store.s3.amazonaws.com/0/40796/d3d61a6f-8845-c62c-d400-d0b0f102f2b4.png)

時間的に1週間の学習量ですね。
また極値が目立ちますが、これはきりのよい数字で起こっています。普通はきりの良い数字でアンケートに答えるのでこうなってしまいますね。

violinplotもしておきます。

```py3
sns.violinplot(survey_df['HoursLearning'])
```

![hours_violinplot.png](https://qiita-image-store.s3.amazonaws.com/0/40796/400e4fdb-fee5-64aa-0738-911445344328.png)

kdeプロットの特徴が反映されています。

また、`countplot`と`kdeplot`の両方を一緒に生成出来る`distplot`というものもあります。
`nan`は削除して使っています。

```py3
hours_learning = survey_df['HoursLearning']
hours_learning = hours_learning.dropna()
sns.distplot(hours_learning)
```

![hours_dist.png](https://qiita-image-store.s3.amazonaws.com/0/40796/76e15f83-043c-8891-296f-9a8593a3e712.png)

ヒストグラムをラグプロットに変えたり、オプションも付けることが出来ます。
便利です！

```py3
sns.distplot(hours_learning, rug=True, hist=False, kde_kws={'color':'indianred'})
```

![hours_dist_2.png](https://qiita-image-store.s3.amazonaws.com/0/40796/8810d89f-272f-412b-5905-55084652ecdc.png)



## SchoolMajor

連続値ですと`kdeplot`が活躍しますが、これはカテゴリ分けなので`countplot`を使います。

```py3
sns.countplot(y='SchoolMajor' , data=survey_df)
```

![major_countplot.png](https://qiita-image-store.s3.amazonaws.com/0/40796/71f31c2a-1229-fe49-b267-4b6520c3414a.png)

見にくいです。。カテゴリ数が多すぎるのですね。
上位10個くらいが見たいです。

### collections.Counter

```py3
from collections import Counter
major_count = Counter(survey_df['SchoolMajor'])
major_count.most_common(10)
```

標準ライブラリの`collections`を使うことでカウントします。
さらに`most_common(10)`とすることでその中の上位10件を取得することが出来ます。

```
[(nan, 7170),
 ('Computer Science', 1387),
 ('Information Technology', 408),
 ('Business Administration', 284),
 ('Economics', 252),
 ('Electrical Engineering', 220),
 ('English', 204),
 ('Psychology', 187),
 ('Electrical and Electronics Engineering', 164),
 ('Software Engineering', 159)]
```

グラフに表示させましょう。

```py3
X = []
Y = []
major_count_top10 = major_count.most_common(10)
for record in major_count_top10:
    X.append(record[0])
    Y.append(record[1])

# [nan, 'Computer Science', 'Information Technology', 'Business Administration', 'Economics', 'Electrical Engineering', 'English', 'Psychology', 'Electrical and Electronics Engineering', 'Software Engineering']
# [7170, 1387, 408, 284, 252, 220, 204, 187, 164, 159]

plt.barh(np.arange(10), Y)
plt.yticks(np.arange(10), X)
```

![major_bar.png](https://qiita-image-store.s3.amazonaws.com/0/40796/64377968-6a14-293e-9571-4b36772d4690.png)

こちらを参考にしました。
[棒グラフ -matplotlib入門](http://yubais.net/doc/matplotlib/bar.html)

`plt.barh`を使うと`plt.bar`の軸を変えることが出来ます。`yticks`を使ってラベルもつけました。
さて、nanは表示させたくないですし、逆順に並び替えたいです。

```py3
X = []
Y = []
major_count_top10 = major_count.most_common(10)
major_count_top10.reverse()
for record in major_count_top10:
    # record[0] == record[0]に関しては下に補足あり
    if record[0] == record[0]:
        X.append(record[0])
        Y.append(record[1])

# ['Software Engineering', 'Electrical and Electronics Engineering', 'Psychology', 'English', 'Electrical Engineering', 'Economics', 'Business Administration', 'Information Technology', 'Computer Science']
# [159, 164, 187, 204, 220, 252, 284, 408, 1387]

plt.barh(np.arange(9), Y)
plt.yticks(np.arange(9), X)
```

![major_barplot_2.png](https://qiita-image-store.s3.amazonaws.com/0/40796/a8df41a3-1938-9a95-9013-4452493c4702.png)

考えていたグラフが出来ました！


### [追記] if record[0] == record[0]: について 

ここは、`NaN`同士の比較のときだけ`False`が返ってくるということでこういう実装をしました。（以下のURLも参照）

[Python, Nanの判定の仕方。](http://nekoyukimmm.hatenablog.com/entry/2015/03/26/095107)

しかし、分かりにくさがあるので
@shiracamus さんに紹介頂きました実装方法のほうが分かりやすいです。私もこちらを今後使おうと思います。

```
if record[0] == record[0]:
```

の部分を以下のように変更。

```
if pd.notnull(record[0]):
```




# データの関連

## Gender と HasChildren

まず、Genderは簡単のために男女だけにします。

```py3
male_female_df = survey_df.where((survey_df['Gender'] == 'male') + (survey_df['Gender'] == 'female') )
```

`countplot`の`hue`を使うことで層別にカウントすることができます。

### countplot(hue)

```py3
sns.countplot('Gender', data=male_female_df, hue='HasChildren')
```

![gender_haschildren_countplot.png](https://qiita-image-store.s3.amazonaws.com/0/40796/2eed906f-479e-205a-ab8b-5f937fd8c90d.png)

男女とも同じような子持ちの割合みたいですね。

## Gender と Age

`countplot`以外のグラフでも層別に表すことが出来ます。`FacetGrid`を使います。

### sns.FacetGrid

```py3
fig = sns.FacetGrid(male_female_df, hue='Gender', aspect=4)
fig.map(sns.kdeplot, 'Age', shade=True)
oldest = male_female_df['Age'].max()
fig.set(xlim=(0, oldest))
fig.add_legend()
```

![gender_age_facetgrid.png](https://qiita-image-store.s3.amazonaws.com/0/40796/21fb6033-dd86-8828-e882-a9bbd8315fc1.png)

若干男性のほうが若い側にありますね。

## EmploymentStatus と Gender

EmploymentStatusは複数あるので上位の数件だけ使いたいと思います。

```py3
# male_female_dfは survey_dfのGenderを男女に絞ったもの
# EmploymentStatusの上位5件を取得
from collections import Counter
employ_count = Counter(male_female_df['EmploymentStatus'])
employ_count_top = employ_count.most_common(5)
print(employ_count_top)
employ_list =[]

for record in employ_count_top:
     if record[0] == record[0]:
        employ_list.append(record[0])

def top_employ(status):
    return status in employ_list

# applyを使ってemploy_listに入った項目の行だけを取得
new_survey_df = male_female_df.loc[male_female_df['EmploymentStatus'].apply(top_employ)]

sns.countplot(y='EmploymentStatus', data=new_survey_df)
```

![employment_status_only_3.png](https://qiita-image-store.s3.amazonaws.com/0/40796/d7638ca4-b917-14a3-2fc1-97659e03145b.png)

これで上位3件の項目だけになりました。

`countplot`のhueを使って男女の層別に見てみます。

```py3
sns.countplot(y='EmploymentStatus', data=employ_df, hue='Gender')
```

![employment_status_hue_gender.png](https://qiita-image-store.s3.amazonaws.com/0/40796/0a6d4c0e-ef58-09fc-356e-b495f70c572d.png)

## EmploymentStatus と HasChildren

まず、HasChildrenを`No->0, Yes->1`に変換しておきます。

```py3
new_survey_df['HasChildren'] = new_survey_df['HasChildren'].map({'No': 0, 'Yes': 1})
```

ここで`factorplot`を使います。`EmploymentStatus`が子供の有無にどれくらい関わっているか見てみます。

### factorplot

```py3
sns.factorplot('EmploymentStatus', 'HasChildren', data=new_survey_df, aspect=2)
```

![factorplot_employment_haschildren.png](https://qiita-image-store.s3.amazonaws.com/0/40796/2d9f1cd3-d96b-376f-9cb9-8f4088cf9484.png)

`Emloyed for wages`が少し値が高いですね。これは納得感があります。

また、factorplotも層別に見ることが出来るので男女に違いがないか見てみます。

```py3
sns.factorplot('EmploymentStatus', 'HasChildren', data=new_survey_df, aspect=2, hue='Gender')
```

![factorplot_employment_hue_gender.png](https://qiita-image-store.s3.amazonaws.com/0/40796/bb4cfb77-eb16-5291-dbfc-c6d1295e2b8d.png)

これはなかなか面白いですね。男女で見ると実は全然違いました。
雇用事情と照らし合わせると色々と考えられることがありそうです。

## Age と HasChildren

### lmplot 

回帰直線で関係を見てみたいと思います。回帰直線には `lmplot`を使います。

```py3
sns.lmplot('Age', 'HasChildren', data=new_survey_df)
```

![lmplot_age_haschildren.png](https://qiita-image-store.s3.amazonaws.com/0/40796/e5c13432-7571-144f-195d-22796c0de4b6.png)

さて、`lmplot`も層別に見ることが出来るのでやってみたいと思います。
まずは`EmploymentStatus`で層別にします。

```py3
sns.lmplot('Age', 'HasChildren', data=new_survey_df, hue='EmploymentStatus')
```

![lmplot_hue_employmentstatus.png](https://qiita-image-store.s3.amazonaws.com/0/40796/64461941-803b-a1f6-9205-21a333237925.png)

一般には `Employed for wages`の値が少し高いですが、前項で見たように男女で分けるともう少しはっきり分かれそうですね。


ついでに、`Gender`でも層別にしておきます。

```py3
sns.lmplot('Age', 'HasChildren', data=new_survey_df, hue='Gender')
```

![lmplot_hue_gender.png](https://qiita-image-store.s3.amazonaws.com/0/40796/42f25cbb-0cc4-e032-501b-d1f48a923b3e.png)

### 複数のグラフを並べて表示する

複数のグラフを並べて表示させる方法もあります。`subplots`を使って2つのグラフを描画しました。

```py3
fig, (axis1, axis2) = plt.subplots(1, 2, sharey=True)
sns.regplot('HasChildren', 'Age', data=new_survey_df, ax=axis1)
sns.violinplot(y='Age', x='HasChildren', data=new_survey_df, ax=axis2)
```

![subplots.png](https://qiita-image-store.s3.amazonaws.com/0/40796/01fa45fd-1efc-2baf-3c55-a1b1ed0970da.png)

`regplot`というのは`lmplot`の低レベルバージョンで、単純な回帰を作る上では同じです。
`regplots`を使ったのは`subplots`で使える関数は`matplotlib Axes`オブジェクトを返すものに限られるようで`lmplot`が使えなかったからです。

詳しい説明は
[Plotting with seaborn using the matplotlib object-oriented interface](http://stackoverflow.com/questions/23969619/plotting-with-seaborn-using-the-matplotlib-object-oriented-interface)
が分かりやすかったです。

この記事でいう "Axis-level" functionは使えるようです。
(regplot, boxplot, kdeplot, and many others)

これに対して "Figure-level" functionも紹介されていてその中に`lmplot`もあります。
(lmplot, factorplot, jointplot and one or two others)

ほとんど`Axis`で一部`Figure`って感じですかね。

それでFigureに関しては`FacetGrid`が良いみたいです。
[Plotting on data-aware grids](http://stanford.edu/~mwaskom/software/seaborn/tutorial/axis_grids.html)


`FacetGrid`でも複数のグラフを並べられます。
Age の分布を EmploymentStatusごとに並べて表示してみました。

```py3
fig = sns.FacetGrid(new_survey_df, col='EmploymentStatus', aspect=1.5)
fig.map(sns.distplot, 'Age')
oldest = new_survey_df['Age'].max()
fig.set(xlim=(0, oldest))
fig.add_legend()
```

![facetgrid_age_haschildren.png](https://qiita-image-store.s3.amazonaws.com/0/40796/39a2d638-5812-d144-1018-a926e38943ea.png)


# おわりに

## 出てくるものの整理

- df.shape
- df.info()
- df.describe()
- df.read_csv
- sns.countplot
- plt.hist
- plt.bar
- sns.kdeplot
- df.loc
- df.map
- sns.rugplot
- sns.boxplot
- sns.violinplot
- sns.distplot
- collections.Counter
- collections.Counter.most_common
- plt.barh
- pd.where
- sns.countplot(hue)
- sns.FacetGrid
- df.apply
- sns.lmplot
- sns.lmplot(hue)
- plt.subplots

## pythonの使い方で参考にしたもの

[Pythonを始めた時から知っていたかったベターな書き方](http://dibtp.hateblo.jp/entry/2014/12/15/232829)
[Python pandas データ選択処理をちょっと詳しく <後編>](http://sinhrks.hatenablog.com/entry/2014/11/18/003204)

## 参考

[【世界で2万人が受講】実践 Python データサイエンス](https://www.udemy.com/python-jp/)
丁寧な解説でわかりやすくておすすめの動画講座です。
質問しても翌日には回答が返ってくるところもポイントです。

[Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理](https://www.amazon.co.jp/Python%E3%81%AB%E3%82%88%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E5%88%86%E6%9E%90%E5%85%A5%E9%96%80-%E2%80%95NumPy%E3%80%81pandas%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E3%83%87%E3%83%BC%E3%82%BF%E5%87%A6%E7%90%86-Wes-McKinney/dp/4873116554)
オライリーのデータ分析の書籍です。よくまとまっています。

[Start Python Club](http://startpython.connpass.com/)
Pythonのコミュニティです。データ分析に特化しているわけではありませんが、Pythonについて幅広く活動されているのでPythonを使うのであれば行ってみると楽しいと思います。