よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

【Python機械学習】pandasの基本的な使い方2

Pandasの使い方その2。
前回その1のつづきです。
機械学習でよく使いそうなものを。

目次


実行環境


Windows10
Anaconda
Python3.7
JupyterLab



Pandasでデータファイルを読み込みDataFrameにする


機械学習する際には、色んな方法で集められまとめられた様々な形式のデータファイルを外部から取り込み、それを読み込んで分析することになります。

Pandasは様々な形式のファイル(CSV、TSV、ExcelJson、pickle、html、clipboard、SQLなど)を簡単に読み込める機能がついており、すぐにDataFrameにできます。

ファイル読み込み用のメソッドとして、

等々、
「readナンチャラ」 という、ファイル形式に対応した名前でいくつも用意されています。
(蛇足ですが書き込み用に 「to
ナンチャラ」 も用意されている。)
詳しくはPandasの公式ページ
IO tools (text, CSV, HDF5, …) — pandas 0.25.3 documentation
でご確認ください。



では実際に取り込んでみます。今回は試しにread_csv()でcsvファイルを読み込んでDataFrameにしてみます。


read_csv()の使い方


df = pd.read_csv(引数)
のようにすれば、変数dfには読み込んだファイルのデータがデータフレームオブジェクトとして格納されます。

引数は

  • csvファイルのパス
  • header=ナントカ
  • names=(カラム名の配列)
  • index_col=ナントカ
  • encoding="ナントカ"

など



csvファイルのパス


読み込みたいcsvファイルのファイルパスを指定します。
カレントディレクトリに保存しているdata.csvという名前のファイルを読み込む場合は"data.csv"または"./data.csv"、など。

だがしかし、 Windowsで動かす場合、
./みたいにMacLinuxのパスの書き方でやってFileNotFoundError: File b'../ナントカ.csv' does not existとエラーが出る事があるそうで。
そんな時はWindows風にバックスラッシュか¥を2つ使って.\\のように書き直すか、
もしくはLinux風パスの書き方の後に, engine='pythonと付け加えればエラー回避できる、らしいです。

分からなければ、カレントディレクトリにcsv ファイルを保存して、"ナントカ.csv"としておけば間違いないです。



encoding 日本語を含むファイルの文字化けやエラー


たぶんこれが一番よく起こる。
エラーの例なら、encoding="ナントカ"を書かずに実行すると
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x83 in position 0: invalid start byte
が出るですとか。
英語のファイルだとencodingを指定せずとも大概すんなりと行くが、 総務省気象庁のWebページに置いてあるような日本語データを含むファイルを読み込む場合はencodingを指定しないとエラーが出たり文字化けしたりが頻発します。
Windowsではエラーが出ず、Linuxだと出るとか、またはその逆とか環境にもよるかも。
そんな時はencoding="shift_jis"などと引数に追加すれば読み込めたりする…。
その場合はshift_jisでもshiftjisでもshift-jisでもSHIFT_JISでも大丈夫みたい。



header 項目行の指定


通常は表の一番上にあり、列名が入っている行をheaderという。header=ナントカで指定する。

  • 書かずに省略する場合 : デフォルトでは「一番上の行が自動的に項目行」にされてしまいます。
  • header=0 : 0すなわち1行目が項目行になります。書かない場合と同じ。デフォルト。
  • header=None : Noneを指定すると「データのどの行にも項目行が含まれていない」という意味になり、「自動的に先頭行に0からの連番で列名」が入る。
  • header=数値 : 数値を行番号で指定すると、csvファイルにある指定行が項目行になる。その行より上にあったデータは無視される。



names=配列 列名をつける


header=Noneで味気ない自動連番の列名がつけられるのが嫌なときや、元々の列名を変えて読み込みたい場合などに使う。
names=リストやタプルの配列で指定。



index_col=数値 行名のある列を指定


通常は表の一番左端に行名がついた列が縦に並んでいるかと思いますが、その列番号を数値でindex_col=0のように書いて指定できます。
0列目が行名の入ったindexになる。
書かない場合は、デフォルトで左端に自動で0からの連番で行名が入る。



usecols=リスト 使う列のみを指定して読み込む


必要なさそうな列を始めっから読み込まず、必要な列だけ指定してデータフレームに取り込む時に使う。

  • 列番号をリストで指定し、usecols=[0,5,7]のように、
  • またはusecols=["単価", "数量"]のように列名をリストで指定。



実践 csvを読み込む


学ぶより慣れろ、こんなもん読んだところで覚えないんで手を動かします。

先にCSVファイルをダウンロードします。
カレントディレクトリに保存することにします。

カレントディレクトリの調べ方
一応書いておきますが、Jupyterを使ってカレントディレクトリ(現在の作業フォルダ)を調べたいときはマジックコマンド(%コマンド名、または!コマンド名)を使うとできます。

カレントディレクトリの確認方法
%pwd

カレントディレクトリの内容物表示
%dir # windows用
%ls  # Linux系用 



では読み込む。

import pandas as pd
import numpy as np # ← 一応インポートした

# 先に保存済みcsvファイルのパス
csv_file = "data.csv"

# csvファイルを読み込んでデータフレーム化。
df_csv = pd.read_csv(
    csv_file, 
    encoding = "shift_jis"
)

# 上5行を表示
df_csv.head()
ダウンロードした時刻:2020/01/01 17:40:24
NaN 太宰府 太宰府 太宰府
年月日 降水量の合計(mm) 降水量の合計(mm) 降水量の合計(mm)
NaN NaN 品質情報 均質番号
1990/1/1 0 8 1
1990/1/2 0 8 1

大宰府の1990年の降水量のデータのようです。元旦に気象庁のサイトからダウンロードしたもの。暇なやつです。

下5行も見てみる。

# 下5行の表示
df_csv.tail()
ダウンロードした時刻:2020/01/01 17:40:24
2018/12/28 0.0 8 1
2018/12/29 0.0 8 1
2018/12/30 0.0 8 1
2018/12/31 0.0 8 1
2019/1/1 0.5 8 1

2019年元旦までのデータが入ってるようです。日付ごとのデータっぽいので約10000万行以上はゆうにありそう。



データ内容の確認


shape 形状の確認

df_csv.shape
(10596, 1)

10596行で1列。あれ?1列?
見た感じ4列あるんだけど。おかしいねぇ。



columns 列名の確認


カラム名を見てみる。

df_csv.columns
Index(['ダウンロードした時刻:2020/01/01 17:40:24'], dtype='object')



1行目を見てみる。

df_csv.iloc[0]
ダウンロードした時刻:2020/01/01 17:40:24    太宰府
Name: (nan, 太宰府, 太宰府), dtype: object



index 行名の確認


行名も確認してみる。

df_csv.index
MultiIndex([(         nan,        '太宰府',        '太宰府'),
            (       '年月日', '降水量の合計(mm)', '降水量の合計(mm)'),
            (         nan,          nan,       '品質情報'),
            (  '1990/1/1',          '0',          '8'),
            (  '1990/1/2',          '0',          '8'),
            (  '1990/1/3',          '0',          '8'),
            (  '1990/1/4',          '0',          '8'),
            (  '1990/1/5',          '0',          '8'),
            (  '1990/1/6',          '2',          '8'),
            (  '1990/1/7',          '0',          '8'),
            ...
            ('2018/12/23',        '3.5',          '8'),
            ('2018/12/24',        '0.0',          '8'),
            ('2018/12/25',        '0.0',          '8'),
            ('2018/12/26',        '2.0',          '8'),
            ('2018/12/27',        '0.0',          '8'),
            ('2018/12/28',        '0.0',          '8'),
            ('2018/12/29',        '0.0',          '8'),
            ('2018/12/30',        '0.0',          '8'),
            ('2018/12/31',        '0.0',          '8'),
            (  '2019/1/1',        '0.5',          '8')],
           length=10596)

multiindexと書いてある。なんか詰め込まれてるんですけど。おせち料理かっ。

header ヘッダーの指定


上の方で少し説明したので繰り返しになりますが、

  • ヘッダー : 表データの一番上に列ごとに項目名を書いた行のこと。


ヘッダー行を指定してcsvを読み直してみる。
4行目(indexが3)をヘッダーにすれば、そこが項目名の行として読み込まれるはずだ!

項目名も無しに、いきなり一番上の行から生の値が入れてあるようなデータを読み込む場合は、header=Noneと指定すると0から始まる数字のカラム名が自動で表示されます。

ってことで、header=3で読み直し。

import pandas as pd
import numpy as np # ← 一応インポートしときます

# csvのパス
csv_file = "data.csv"

# csvファイルを読み込んでデータフレーム化。
df_csv = pd.read_csv(
    csv_file, 
    encoding = "shift_jis",
    header = 3 # ← 追加した
)

# 上5行を表示
df_csv.head()
Unnamed: 0 Unnamed: 1 品質情報 均質番号
0 1990/1/1 0.0 8 1
1 1990/1/2 0.0 8 1
2 1990/1/3 0.0 8 1
3 1990/1/4 0.0 8 1
4 1990/1/5 0.0 8 1

header=3で指定した行より上は無視されます。取り込まれない。

shape 形状の確認


# 形状の確認
df_csv.shape
(10593, 4)

4列になり、表の左端にindex番号が自動で割り振られました。
よし、これでいこ。

dtypes 型の確認


列ごとのデータのタイプ

df_csv.dtypes
Unnamed: 0     object
Unnamed: 1    float64
品質情報            int64
均質番号            int64
dtype: object



describe() 統計量の確認


列ごとに以下のものが表示される。上から順に、

  • count : 要素の個数
  • mean : 平均値
  • std : 標準偏差
  • min : 最小値
  • 25% : 1/4分位数、第1四分位数、25パーセンタイル
  • 50% : 中央値、50パーセンタイル
  • 75% : 3/4分位数、第3四分位数、75パーセンタイル
  • max : 最大値
df_csv.describe()
Unnamed: 1 品質情報 均質番号
count 10587.00000 10593.000000 10593.0
mean 4.97086 7.988389 1.0
std 15.47880 0.247261 0.0
min 0.00000 0.000000 1.0
25% 0.00000 8.000000 1.0
50% 0.00000 8.000000 1.0
75% 2.00000 8.000000 1.0
max 332.00000 8.000000 1.0

日付の列がどっか行きましたけど。object型だからかな?

分位数
分位数 - Wikipedia



info() データの型、項目名や数などの情報確認


info()は頻繁に使うメソッドのようです。
データのタイプや欠損の具合などがまとめて確認できて便利。

df_csv.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10593 entries, 0 to 10592
Data columns (total 4 columns):
Unnamed: 0    10593 non-null object
Unnamed: 1    10587 non-null float64
品質情報          10593 non-null int64
均質番号          10593 non-null int64
dtypes: float64(1), int64(2), object(1)
memory usage: 331.2+ KB


上から

  • データのクラス(データフレームクラス)
  • RangeIndex : indexの範囲 0~10592までの10593個
  • Data columns : 合計4列
  • 項目名別で、non-null欠損値のない行数と値の型
  • dtypes 列ごとの値の型
  • モリー使用量



nunique() 重複しないユニークな値の個数を確認


列ごとの重複しないデータの個数。
「特・上・並」とか「A・B・C」とかでカテゴリー別けがしてあるようなデータの、カテゴリー毎の個数を見たりする時によく使うようです。

df_csv.nunique()
Unnamed: 0    10593
Unnamed: 1      213
品質情報              5
均質番号              1
dtype: int64

一番上のは日付データなので全行異なる。
品質情報が「5」というのは5段階に別けてあるってことだろう、知らんけど。



isnull() 欠損値をbool型で確認


欠損値かどうかをTrueとFalseのbool型で返す。

  • True : 欠損している
  • False : 欠損していない

要するに値がNaNならTrueと表示され、そうでなければFalseと表示される。

df_csv.isnull()
Unnamed: 0 Unnamed: 1 品質情報 均質番号
0 False False False False
1 False False False False
2 False False False False
3 False False False False
4 False False False False
... ... ... ... ...
10588 False False False False
10589 False False False False
10590 False False False False
10591 False False False False
10592 False False False False

10593 rows × 4 columns

何万行もあるデータを1つずつ確認するのは大変。
そこで次。



isnull().sum() 列ごとの欠損数


df.isnull().sum()とすれば、欠損値の列ごとの合計個数(Trueの個数)が確認できる。

df_csv.isnull().sum()
Unnamed: 0    0
Unnamed: 1    6
品質情報          0
均質番号          0
dtype: int64

「Unnamed:1」なる列は6個の欠損値があることを示している。
その他の列には欠損値ゼロ。



欠損値のある行だけを抽出


欠損値の処理方法をこの後で書くのですが、処理方法を決める為に欠損値がどんな具合なのかを見たいので、それだけを抽出してみます。



幸いにも列名「Unnamed:1」つまりcolumns=1番の列のみが欠損しているので、とりあえずその列を全行抽出。

df_csv.iloc[ : , 1]
0        0.0
1        0.0
2        0.0
3        0.0
4        0.0
        ... 
10588    0.0
10589    0.0
10590    0.0
10591    0.0
10592    0.5
Name: Unnamed: 1, Length: 10593, dtype: float64

上で抽出したものについてisnull()で欠損のbool型にする。

df_csv.iloc[ : , 1].isnull()
0        False
1        False
2        False
3        False
4        False
         ...  
10588    False
10589    False
10590    False
10591    False
10592    False
Name: Unnamed: 1, Length: 10593, dtype: bool

行が省略されていて見えないが、欠損値はTrueになっている筈。
上の抽出について、Trueの値がある行のみを全体から抽出。

# 欠損値のある行のみを抽出、変数に格納
df_csv_na = df_csv[df_csv.iloc[ : , 1].isnull() == True]

df_csv_na
Unnamed: 0 Unnamed: 1 品質情報 均質番号
1114 1993/1/19 NaN 0 1
1115 1993/1/20 NaN 0 1
7317 2010/1/13 NaN 1 1
7318 2010/1/14 NaN 0 1
7319 2010/1/15 NaN 0 1
7320 2010/1/16 NaN 0 1

欠損値NaNのある行だけを抽出できた。
後で使いたいので変数にした。

なぜ欠損してるんだろうと思って1993.1.19のできごとを調べてみたら、今上天皇の婚姻正式決定された日だそうで、特に関係無さそう。それ以外の日もこれと云った理由も見つからず。



dropna()とfillna() 欠損値の処理


データが所々抜けているとき、その抜けた値を欠損値といい、NaNと表示されます。
Not A Numberの略。

欠損値があるとデータの集計や分析が上手くできなかったりするので、それをどう処理するかがデータ分析者の力の見せ所のひとつみたいです。

Googleがやっている機械学習サイトのkaggleには沈没したタイタニックの乗客の生存データが置いてありますが、もしキャビン(部屋番号)によって生存率に差があるような事が分かるなら客船の設計に何か活かしたりできそうです。しかし、あいにくキャビンのデータは欠損だらけなので、その観点からの分析は諦めないとなりません。

話しを戻そう。

欠損値の処理方法として

  • dropna() : 欠損値を削除する
  • fillna() : 欠損値を別の値で置き換える

といったものがある。

dropna( ) 欠損値の削除


dropna()は引数で幾つかのパラメータを取る。

    • how
      1. how="any" : デフォルト。欠損値が1つでもある行を削除する。
      2. how="all" : 全ての値が欠損値の行を削除する。
    • thresh
      1. thresh=数値 : 欠損値ではない値が数値以上の個数残っている行を残して後は削除する。
    • subset
      1. subset=[列リスト] : リストの列に欠損値がある行を削除する。
    • axis(欠損値をチェックする軸の方向)
      1. axis=0 : デフォルト。ある行方向で欠損値があるかをチェックする
      2. axis=1 : ある列方向で欠損値があるかをチェックする
    • inplace
      1. inplace=False : デフォルト。新しいオブジェクトを返す。元データは上書きされない。
      2. inplace=True : 元データのオブジェクトを上書きして返す。


いくつか確認する。

さっき作った欠損値だけのデータ

df_csv_na
Unnamed: 0 Unnamed: 1 品質情報 均質番号
1114 1993/1/19 NaN 0 1
1115 1993/1/20 NaN 0 1
7317 2010/1/13 NaN 1 1
7318 2010/1/14 NaN 0 1
7319 2010/1/15 NaN 0 1
7320 2010/1/16 NaN 0 1

dropna()で何のパラメータも指定しない場合


1つでも欠損値がある行は容赦なく削除します。
もし、大して重要ではないが欠損値を多く含む列(例えば備考欄)が付いていたら、他の重要なデータはことごとく削除されてしまいます。
これはやり過ぎる方法で注意。

df_csv_na.dropna()
Unnamed: 0 Unnamed: 1 品質情報 均質番号

欠損値が全て消えました。



thresh=数値
欠損数していない値が数値以上の行は残して後は削除。
thresh=4とすると、欠損していない値が4つ以上ないと行を削除するので、全行が削除される。
thresh=3なら、3個以上の欠損値ではない行が残るので全行残る。

df_csv_na.dropna(thresh=4)
Unnamed: 0 Unnamed: 1 品質情報 均質番号

how="any"でaxis=1列方向削除
how="any"とaxis=1を組み合わせると、1つでも欠損値のある列は削除される。

df_csv_na.dropna(how="any", axis=1)
Unnamed: 0 品質情報 均質番号
1114 1993/1/19 0 1
1115 1993/1/20 0 1
7317 2010/1/13 1 1
7318 2010/1/14 0 1
7319 2010/1/15 0 1
7320 2010/1/16 0 1



fillna() 欠損値の置換


欠損値を何かの値で置換する時に使う。

fillna(置換後の値)

# fillna(置換したい値)
df_csv_na.fillna("何か代替値")
Unnamed: 0 Unnamed: 1 品質情報 均質番号
1114 1993/1/19 何か代替値 0 1
1115 1993/1/20 何か代替値 0 1
7317 2010/1/13 何か代替値 1 1
7318 2010/1/14 何か代替値 0 1
7319 2010/1/15 何か代替値 0 1
7320 2010/1/16 何か代替値 0 1

fillna(df.mean()) 平均値に置換


平均値は項目での平均。数値データの入った列で使う。

df_csv.dtypes
Unnamed: 0     object
Unnamed: 1    float64
品質情報            int64
均質番号            int64
dtype: object
# 平均値で置換
df_csv_fill_mean = df_csv.fillna(df_csv.mean())
# 欠損値の個数
df_csv_fill_mean.isnull().sum()
Unnamed: 0    0
Unnamed: 1    0
品質情報          0
均質番号          0
dtype: int64
# 列1における欠損値のみに対して平均値で置換する処理
df_csv["列1"].fillna(df_csv["列1"].means())



カテゴリーデータの操作


value_counts() カテゴリーとデータの数を確認


カテゴリー別け用のデータが入った列に対して、カテゴリー毎の個数をカウントする。

# データのユニーク数確認
df_csv.nunique()
Unnamed: 0    10593
Unnamed: 1      213
品質情報              5
均質番号              1
dtype: int64
# 品質情報の列におけるカテゴリー毎の個数
df_csv["品質情報"].value_counts()
8    10566
4       13
5        8
0        5
1        1
Name: 品質情報, dtype: int64

特定のカテゴリーのデータのみ抽出

# 品質情報列のカテゴリー4を抽出
df_csv[df_csv["品質情報"] == 4]
Unnamed: 0 Unnamed: 1 品質情報 均質番号
621 1991/9/14 33.0 4 1
1113 1993/1/18 0.0 4 1
1116 1993/1/21 0.0 4 1
6616 2008/2/12 0.0 4 1
6617 2008/2/13 0.0 4 1
6659 2008/3/26 0.0 4 1
6660 2008/3/27 0.0 4 1
6661 2008/3/28 0.0 4 1
6662 2008/3/29 1.0 4 1
7316 2010/1/12 0.0 4 1
7321 2010/1/17 0.0 4 1
8843 2014/3/19 0.0 4 1
8844 2014/3/20 4.0 4 1



カテゴリー変数の欠損値を置換し抽出


mode()で最頻値で埋めて抽出する。上書き無し。
カテゴリー別けした列Xにおける欠損値を、その列の最頻値で置換するには次のようにする。

df_csv["列X"].fillna(df_csv["列X"].mode()[0])



グループ化して各種統計量を計算する

df_csv.groupby("品質情報")
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x746e2d3910>
df_csv.groupby("品質情報").sum()
Unnamed: 1 均質番号
品質情報
0 0.0 5
1 0.0 1
4 38.0 13
5 30.5 8
8 52558.0 10566
df_csv.groupby("品質情報").mean()
Unnamed: 1 均質番号
品質情報
0 NaN 1
1 NaN 1
4 2.923077 1
5 3.812500 1
8 4.974257 1
df_csv.groupby("品質情報").max()
Unnamed: 0 Unnamed: 1 均質番号
品質情報
0 2010/1/16 NaN 1
1 2010/1/13 NaN 1
4 2014/3/20 33.0 1
5 2016/6/17 28.0 1
8 2019/1/1 332.0 1



DataFrameの結合


  • DataFrame同士、DataFrameとSeriesの結合か、
  • 行か列のどちらに繋げるか、
  • 重複する行か列をどうするか、

といった問題によって結合や追加の方法がいくつもある。
append、join、concat、merge、等々。
今回はconcat()だけを簡単に触れておこ。

concat()


引数は、axis=0, axis=1, sort=True、など。



データフレームを2つ作って、行方向と列方向の二種類で結合させてみる。

# 1個目のnumpy配列生成
n1 = np.random.randint(0,101,9).reshape(3,3)

# データフレーム化
df1 = pd.DataFrame(n1)
df1
0 1 2
0 12 11 81
1 81 50 53
2 81 97 75
# 2個目のnumpy配列生成
n2 = np.random.rand(9).reshape(3,3)

# データフレーム化
df2 = pd.DataFrame(n2)
df2 
0 1 2
0 0.216004 0.948614 0.580056
1 0.550950 0.541745 0.635392
2 0.066377 0.619564 0.080148

何も考えずそのまま接続すると?

pd.concat([df1, df2])
0 1 2
0 12.000000 11.000000 81.000000
1 81.000000 50.000000 53.000000
2 81.000000 97.000000 75.000000
0 0.216004 0.948614 0.580056
1 0.550950 0.541745 0.635392
2 0.066377 0.619564 0.080148

ただ単に上下に積み重ねられた。
全ての値がfloat型になっている。



次はaxis=1を指定して横方向に結合。

pd.concat([df1, df2],  axis=1)
0 1 2 0 1 2
0 12 11 81 0.216004 0.948614 0.580056
1 81 50 53 0.550950 0.541745 0.635392
2 81 97 75 0.066377 0.619564 0.080148



apply(関数名) 関数の適用


Excelのセルに関数を入れる感じで、行や列に関数を使いたい場合に使うと便利。
サラッと流す。

def tax(x):
    return x * 0.1
df_csv["列1"].apply(tax)



複数の引数を取る場合


def plus(x, y):
    return x + y
plus(df_csv["列1"], df_csv["列2"])



dfを引数に取る方法

def add(df):
    return df["列1"] + df["列2"]
df.apply(add, axis=1)



複数の戻り値がある場合

def square_and_cube(x):
    return pd.Series([x**2 , x**3])
df(["suqared", "cube"]) = df["列1"].apply(square_and_cube)
df



長くなった。今回はここまで。
Pandasの使い方については今後実際に機械学習を進めて、前処理でめちゃめちゃよく使うテクニックがあればその都度取り上げることにする。