学生による学生のためのデータサイエンス勉強会

【Pythonによるデータ分析】Pandasの基本操作5選

historoid
historoid

こんにちは。清野(@historoid)です。

今回は、Pythonデータ分析に必須のライブラリ、Pandasの紹介です。

Pythonによるデータサイエンスが流行している現在、Pandasを触ったことがある人も多いと思います。

import pandas as pdを何度書いたことか……、なんて人も多いでしょう。

今回は、そんなPandasの基本的な使い方を紹介します。基本さえ理解していれば複雑なテーブル操作も楽ちんですよ。

Pandasの基本操作5選

さっそくPandasの基本的な使い方を見ていきましょう。

今回選んだのは以下の5つです。

  1. データの読み込み
  2. 基本統計量の表示
  3. 欠損値の確認
  4. 行と列の指定
  5. 条件によるデータの抽出

この5つは、私がデータ解析をするときに必ず行う操作です。

そしてこの順番のとおりに作業しています。とくに上の3つは必ず行います

データの読み込み

まずはデータを読み込まないことには始まりません。

Google Drive上のデータをGoogle Colabから読み込む方法

私はGoogle Colabをよく使うので、Google Drive上にあるデータ(エクセルファイル)を読み込む方法を紹介します。

# Google DriveをGoogle Colabにマウント
from google.colab import drive
drive.mount('/content/drive')

この操作で、Google Colabマシンの/content/driveにあなたのGoogle Driveが接続されますが、認証作業が必要になります。

上記のコードを実行すると以下のようなURLが表示されます。

URLをクリックしてください。すると自分のGoogleアカウント選択画面に遷移します。

そしてデータが置いてあるGoogle Driveのアカウントを選択してください。

そして「許可」します。

そしてこのコードをコピーして、Google Colabの画面に戻ってください。

そして先ほどのフォームにコピペします。これでOKです。

import pandas as pd
df = pd.read_excel('/content/drive/My Drive/xxxx/xxxx.xlsx')

これでdfにはエクセルファイルの中身が入りました。

/content/drive/My Drive/まではみんな同じです。Google Driveのマイドライブの中身がMy Drive/です。

例えば、マイドライブに「実験結果」というフォルダがあり、「フィールドワーク.xlsx」というエクセルファイルがあれば、/content/drive/My Drive/実験結果/フィールドワーク.xlsxと入力してください(ファイル名は基本的には英語で管理した方がいいです)。

WindowsとMacの違い

フォルダの区切りを示すスラッシュ/は、Windowsの場合は¥になります。

複数シートからなるエクセルファイル

もしエクセルファイルが複数シートであれば、pd.read_excel('xxxx/xxxx', sheet_name=None)と引数を足してください。

これで複数シートからなるdfが作られます。df['sheet_name_xxxx']とシート名を指定すれば、その1枚分のシートが表示されます。

df

これだけでセルを実行すると、dfの中身が表示されます。print(df)でもOKです。

Google Colab上のCSVファイルを読み込む

実はGoogle Colabには、サンプルデータがあらかじめ入っています。

!ls
# sample_data

!ls sample_data/
# anscombe.json		      mnist_test.csv
# california_housing_test.csv   mnist_train_small.csv
# california_housing_train.csv  README.md

!lsはGoogle Colabマシンに対するbashコマンドであり、Pythonコマンドではありません(意味がわからなくてもOKです)。

!をつけることで、Google Colab(Jupyter)上からシェルコマンドを使えています。

lsコマンドではフォルダの中身を見ることができます。その結果がsample_dataです。

つまり「sample_dataってフォルダがあるよ」という意味です。それに対して、ls sample_dataとすることで、今度はsample_dataの中身を見ているわけです。

その結果、いくつかのファイルが見つかります。

.csvファイルがあったので、pd.read_csv()で読み込みます。

import pandas as pd
df = pd.read_csv('sample_data/california_housing_train.csv')

このように、もとからGoogle Colabに入っていれば、from google.colab import driveのくだりは必要ないわけですね。

基本統計量の表示

さて、データの読み込みが長くなりましたが、あとはさらっと進めましょう。

まずは基本統計量の表示です。これでまずデータ全体をざっと確認します。

df.describe()

たったこれだけです。

これで、列(説明変数)ごとの基本統計量を確認することができます。

count欠損値を除くデータの個数
mean平均値
std標準偏差
min最小値
25%小さい方から25%目の値
50%中央値
75%小さい方から75%目の値
max最大値

すごく便利ですよね。

この作業をエクセルでしようと思ったら、結構時間がかかります。

しかもエクセルではデータ量が増えると大変です。エクセルは高機能なアプリですが、データの選択が面倒なのです。

なおこのdf.describe()で表示されるのは、計算可能な列だけです。

説明変数の中に文字列や時系列データが混ざっていると、

欠損値の確認

次に重要なのが欠損値の確認です。

口を酸っぱくして言い続けますが、データ解析を始める前に欠損値の確認をしてください。

というのも、欠損値はPandasの内部では、NaN(Not a Number: 非数)として処理されるためです。

そしてNaNは計算できません。NaN+1は、NaNになります。

つまり、計算過程の中にNaNが混ざると、答えはNaNになってしまうのです。

苦労して「このデータとこのデータを抽出して……」とかやった計算がNaNになったり、エラーになります。

df.isna().any()

これで欠損値の確認を行うことができます。

Falseが返ってくると「欠損値はないよ」という意味です。

df.isna()

これだけだと、データフレームのすべての要素に対してTrueFalseが返ってきます。

これを.any()とすることで、「1つでもあればTrueを返せ」とするわけです。

行と列の指定

さて、今度は必要な行や列の指定を行いましょう。

行数や列数による指定

# 0行目を抽出
df.iloc[0]

数値による指定は、ilocメソッドを使います。

Pandasでは行の指定が先にきますので、iloc[0]と書けば、自動的に0行目を意味します。

# 0列目を抽出
df.iloc[:,0]

これは正確には「すべての行の0列目」を指定しています。まずiloc[:]で「すべての行」という意味です。

そこでカンマで区切って、列の情報を追加します。iloc[:, 0]とすると、「0列目」という指定が追加されたわけです。

スプライシングによる指定

応用です。ilocを使って、0行目から100行目までを抜き出しましょう。

# 0行目から100行目まで
df.iloc[0:100]
注意

Pythonのリストも同様ですが、[0:100]としたときは、\(0\)以上\(100\)未満が選ばれます(つまり\(99\)までが抽出されます)。

ややこしいように感じるかもしれませんが、以下のように考えてください。

数直線上で、「0」の左側に境界線を引き、「100」の左側に境界線を引きます。境界線の間の数字を抽出します。

ほら。こう考えれば、100は含まれませんよね?

列名による指定

# longitude列を抜き出す
df['longitude']

簡単ですね。列名をブラケット[]の中に入れればOK。シングルクォート''を忘れずに!!

# 列名を知りたいなら
df.columns

先に書いたdf.describe()の時点で列名は表示されていますが、念の為。

ドット記法で列名指定

個人的にはあんまり好きではないのですが、込み入ってくるとこれじゃないと書けない場合もあります。

# longitude列を抜き出す
df.longitude

はい。かなり直接的な書き方ですね。列名をアトリビュート(属性)としてコールするわけです。

複合的な書き方

ではこれまで書いた方法を使って、複合的な条件で抜き出してみましょう。

以下はすべて同じ要素が返ってきます

# はじめの10行のlongitude列を抜き出す。

# 1
df.iloc[0:10, 0]

# 2
df.iloc[0:10]['longitude']

# 3
df.iloc[0:10].longitude

他にも書き方はありますが、基本的にこれでOKです。というかこれで書いておくれ (・・;

条件によるデータの抽出

では最後の項目です。条件によるデータの抽出をやっていきましょう。

例えば「血圧が120以上のひとの人数を知りたい」とか「価格が1万円以上の商品の購入者の年齢が知りたい」など、データ分析には条件によるサンプリングが不可欠です。

条件はブラケット[]の中に書く

Pandasの操作は簡単です。

ではlatitude列の値が34より大きいデータを抜き出してみましょう。

# まずlatitude列を抜き出す(どの列でもいいよ)
row_1 =  df['latitude']

# latitude列の値が34.0より大きかの判定
result = row_1 > 34.0
'''
0         True
1         True
2        False
3        False
4        False
         ...  
16995     True
16996     True
16997     True
16998     True
16999     True
'''

途中ですけど、ここまで大丈夫ですか?

row_1 > 34.0とすることで、row_1の要素のうち、34より大きいものはTrue、小さいのもはFalseになりました。その結果をresultに代入してます。

df[result]

これで抽出できました。簡単!!

まとめます。せっかくなので違う列で試してみます。

# 条件をつけたい列を取り出す。
row_households = df['households']

# 条件の設定
condition = row_households < 450

# データフレームからの抽出
df[condition]

こんな感じです。

でもわざわざ変数に置き換えるのも面倒です。一行でまとめちゃいましょう。

df[df['households']<450]

すっきりしましたね。これだけでいいんですよ。

多重条件

条件を多重にしても基本は変わりません。「条件はブラケットの中」です。

# 1つ目の条件
c1 = df['households']<450

# 2つ目の条件
c2 = df['latitude']<34.0

# 多重条件で抽出
df[c1 & c2]

つまり&で条件を足しただけですね。

これも変数に置き換えずに一行で書けます。

df[(df['households']<450) & (df['latitude']<34.0)]

気をつけてください!!

変数に置き換えないときは、括弧()が必要です。

OR条件

上記の例はANDでしたが、ORも指定できます。

df[(df['households']<450) | (df['latitude']<34.0)]

簡単ですね。&のかわりに|を使っただけです。

標本数を数える

条件で抜き出したからには、それがいくつあるのか知りたいでしょう。

# 条件
mask = df['households']<450

# 条件に合致する標本のカウント
mask.sum()

sum()は合計値を取りますが、Trueは1、Falseは0としてカウントされます。そのためmaskに対して合計値を求めると標本数がわかるのです。

以下に間違いやすい例を書きます。

# 条件
mask = df['households']<450

# ダメな例
df[mask].sum()
# それぞれの列の合計値が算出される

# 良い例
len(df[mask])
# len()で行列の行数をカウントできる

条件で抽出し終わってからsum()にしちゃうと、列ごとの合計値が出てしまいます。

その他の操作

他にも使いそうなものを挙げておきます。

欠損値に対する処理

すべてが欠損値の行や列を削除する

# 1行(標本を1つ)削除
df.dropna(how='all')

# 1列(説明変数を1つ)削除
df.dropna(how='all', axis=1)  # axis=0は行、axis=1は列を示す

1つでも欠損値を含む行や列を削除する

# 1行(標本を1つ)削除
df.dropna(how='any')

欠損値を穴埋めする

# 欠損値を0で穴埋め
df.fillna(0)

# 平均値で穴埋め
df.fillna(df.mean())

# 最頻値で穴埋め
df.fillna(df.median())

欠損値を含む行を抽出する

df.iloc[:, df.isnull().any()]

要素に関数を適用させる

# 例えば2倍にする
def double(x):
    return x*2

df.applymap(double)

# 適用先がSeriesなら
df.map(double)

ユニーク(一意)な値の個数をカウントする

ユニークな値を取得する

# Seriesに対して
row_xxxx.unique()

ユニークな値をカウントする

# Seriesに対して
row_xxxx.value_counts()

インデックスを振り直す

要素の値でソートする

df.sort_values('row_name_xxxx')
# 引数inplace=Trueで元のオブジェクトを変更
# デフォルトではソートされた新しいdfを生成

インデックスでソートする

df.sort_index()

# 列名でソート
df.sort_index(axis=1)

インデックスを振り直す

df.reset_index()
# index列が自動的に生成され、もとのインデックスが保存される
# 新しいデータフレームが生成されるので変数に代入すること

# もとインデックスが不要なら
df.reset_index(drop=True)

# もとのオブジェクトに上書き
df.reset_index(drop=True, inplace=True)

列名の変更

# 変更は辞書で行う
df_new = df_old.rename(columns={'origin':'new_one', 'origin2':'new_one2'})

行名の変更

# 変更は辞書で行う
df_new = df_old.rename(index={'origin':'new_one', 'origin2':'new_one2'})

historoid
historoid

いやー。長かったですね。

基本だけでこんなに長いんかい、と思うかもしれませんが、そんなに多機能なことをしないと前処理できないってことなんです。

機械学習ライブラリに付属するデータセットで勉強しているうちはあまり感じませんが、実際に他の人から持ち込まれたエクセルを処理するようになると、Pandasのすごさがよく分かります。

今回は以上です。長くなりましたが、辞書的に使ってください。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です