ろぐれこーど

限界組み込みエンジニアの学習記録とちょっぴりポエム

(pandas)Series, DataFrameの基本的な使い方を学びたかった

pythonにはpandasという非常に優れたデータ解析ツールが用意されている。昔はデータ解析といえばRがよく用いられていたらしいが、 pandasやnumpyなどの便利なツールが開発され、pythonが爆発的に普及していったらしい。

そんなわけで勉強がてらpandasの基礎的な部分の使い方を少し学んだのでメモする。

Series

pandasはSeriesとDataFrameという二つのデータ構造を持っており、これらを使ってデータに対して様々な操作を行なっていく。Seriesがベクトルのような一つの系列、DataFrameは行列のような複数系列のデータに対応している。
Seriesは以下のように宣言する。

from pandas import Series
s = Series([10, 32, 31.3, 26, 9])

# 0    10.0
# 1    32.0
# 2    31.3
# 3    26.0
# 4     9.0
# dtype: float64

引数はlistでもnp.arrayでもtupleでもよい。与えたデータに対してindexがつけられ、一つのオブジェクトとして扱われる。indexは指定しない場合はrange()によって自動で整数が割り当てられるが、自分で指定することもできる。

s = Series([10, 32, 31.3, 26, 9], index=list("abcde"))

# a    10.0
# b    32.0
# c    31.3
# d    26.0
# e     9.0
# dtype: float64

もちろんデータの数と同じにする必要はある。indexは後から代入によって指定することもできる。

s = Series([10, 32, 31.3, 26, 9], index=list("abcde"))
s.index = ["01", "02", "03", "04", "05"]

# 01    10.0
# 02    32.0
# 03    31.3
# 04    26.0
# 05     9.0
# dtype: float64

Seriesからデータの値(values)やindexのみを取り出すこともできる

s = Series([10, 32, 31.3, 26, 9], index=list("abcde"))
s.index

# Index(['a', 'b', 'c', 'd', 'e'], dtype='object')


s.values

# array([ 10. ,  32. ,  31.3,  26. ,   9. ])

気をつけるべき点として、s.valuesではnp.arrayが返されるのに対し、s.indexではpandas内のIndexオブジェクトが返される(indexの型によって変わる)。indexもnp.arrayのように扱いたい場合はs.index.valuesとする必要があるが、s.indexに対してもnumpyのようなスライシングは可能なのであまり問題にならないかもしれない。

Seriesに対しても、listなどの配列と同じように要素を取り出すことができる。

s["b"]

# 32.0

実は要素を取り出す方法がloc, iloc, ixなどいくつかあるのだが、DataFrameのところで説明することにする。

Seriesに対して、np.arrayと同じように算術演算や数学関数を適用することもできる。ただしSeries同士の演算を行うと、indexが対応していない場合に欠損値(Not a number, nan)が出てくる。これらの処理はいくつかあるが、とりあえずここでは扱わない。以下のリンクで詳しい説明があるので気になったら読む。

pandasでよく使う文法まとめ - Qiita
データ欠損の状況を把握する - Python vs. R - Qiita
pandas によるデータセットの加工 (1) - Qiita

実務で使用する場合にはかなり重要な部分になってくると思うが、必要になったらまた勉強するってことで・・・

最後に、Seriesは辞書型の変数を突っ込んでも宣言できる。また、Seriesのindexにはname属性があり、代入することでnameを指定できる。

data = {"shooter":32, "maneuver":15, "charger":5, "roller":21 }
s = Series(data)
#--------------
# charger      5
# maneuver    15
# roller      21
# shooter     32
# dtype: int64
#--------------

s.name = "weapons"
#-----------------
# charger      5
# maneuver    15
# roller      21
# shooter     32
# Name: weapons, dtype: int64
#-----------------

辞書のキーがindexに相当することになるが、indexを別に指定することもできる。dataにないindexを指定した場合は欠損値となる。

data = {"shooter":32, "maneuver":15, "charger":5, "roller":21 }
weapons = ["shooter", "charger", "maneuver", "roller", "blaster"]
s = Series(data, index=weapons)
# shooter     32.0
# charger      5.0
# maneuver    15.0
# roller      21.0
# blaster      NaN
# dtype: float64

indexを指定しない場合、辞書のキーはアルファベット順にソートされてindexに使われる。指定したらその順番になる。

DataFrame

Data Frameは行と列を持つデータ構造であり、Seriesを一つの列とするオブジェクトとみなせる。各列(Series)はそれぞれintやboolなど異なる型を扱え、特定の列や行に対して種々の操作を行うことができる。

DataFrameの宣言方法はいくつかあるが、とりあえずわかりやすいのは辞書による宣言。

from pandas import DataFrame
data = {"weapons":["shooter", "charger", "maneuver", "roller"],
        "count":[23, 5, 13, 21],
        "rate":[0.3, 0.5, 0.2, 0.6] }
df = DataFrame(data, index=list("abcd"))

#   count  rate   weapons
# a     23   0.3   shooter
# b      5   0.5   charger
# c     13   0.2  maneuver
# d     21   0.6    roller

countとrateとweaponsというSeriesがdfに入っているような構造である。順番を指定しなければアルファベット順となる。列指定はcolumns引数に渡せば良い。

df = DataFrame(data, index=list("abcd"), column=["weapons", "count", "rate"])

#     weapons  count  rate
# a   shooter     23   0.3
# b   charger      5   0.5
# c  maneuver     13   0.2
# d    roller     21   0.6

Seriesと同様の方法で、indexやcolumnsを取り出すことができる。

data = {"weapons":["shooter", "charger", "maneuver", "roller"],
        "count":[23, 5, 13, 21],
        "rate":[0.3, 0.5, 0.2, 0.6] }
df = DataFrame(data, index=list("abcd"))

df.columns
# Index(['count', 'rate', 'weapons'], dtype='object')

df.index
# Index(['a', 'b', 'c', 'd'], dtype='object')

DataFrameの各列、すなわちSeriesは以下のようにして取り出せる。

df["count"]
# a    23
# b     5
# c    13
# d    21
# Name: count, dtype: int64

nameにcountが入っていることが確認できる。一応df.countという風に書いても取り出せる。ただし、この辺はnumpyのインデクシングとは結構違ってくるので、 あとで述べるindexing, slicingの項の方法で統一した方がわかりやすいと思う。

DataFrameもvaluesによって値をnp.arrayで取り出せる。

df.values
# array([[23, 0.3, 'shooter'],
#        [5, 0.5, 'charger'],
#        [13, 0.2, 'maneuver'],
#        [21, 0.6, 'roller']], dtype=object)

ただし例のように、多くの場合二次元配列にstrやint, floatが混ざっているので取り扱いには気をつける。

numpyよろしく行列を転置させることもできる。

data = {"weapons":["shooter", "charger", "maneuver", "roller"],
        "count":[23, 5, 13, 21],
        "rate":[0.3, 0.5, 0.2, 0.6] }
df = DataFrame(data, index=list("abcd"))
df.T
#                a        b         c       d
# count         23        5        13      21
# rate         0.3      0.5       0.2     0.6
# weapons  shooter  charger  maneuver  roller

辞書による宣言を紹介したが、二次元配列を与えて宣言することもできる。もちろんindexとcolumnsを指定できる。

a = np.random.rand(3, 4)   #適当にデータを作る
df = DataFrame(a, index=range(1, 4), columns=list["wxyz"])
#           w         x         y         z
# 1  0.245705  0.242101  0.427463  0.058253
# 2  0.018467  0.001331  0.174376  0.893390
# 3  0.705698  0.800905  0.138320  0.270641


indexing, slicing あれこれ

Series、DataFrame共にnumpy likeなスライシングが可能であり、様々な計測値などが混在するデータから特定のものを容易に取り出すことができる。しかし同じことをするにしても方法がいくつかあり、ややこしいので軽くまとめる。

[]だけで書く

DataFrameから列のみを取り出す方法として、普通に[]をつける、というものがある。

data = {"weapons":["shooter", "charger", "maneuver", "roller"],
        "count":[23, 5, 13, 21],
        "rate":[0.3, 0.5, 0.2, 0.6] }
df = DataFrame(data, index=list("abcd"))
df["count"]
# a    23
# b     5
# c    13
# d    21
# Name: count, dtype: int64

この方法ではDataFrameを列Seriesの集合とみなし、特定の一列だけを取得する。複数列を取得することはできない。また行を指定することもできない。 なので慣れないうちは下で述べるlocilocで統一した方がわかりやすいと思う。

locを使う

locメソッドを使うと、DataFrameの行と列をラベル(indexとcolumn)で指定して取り出せる。b行のrate列の値を取り出す場合は以下

data = {"weapons":["shooter", "charger", "maneuver", "roller"],
        "count":[23, 5, 13, 21],
        "rate":[0.3, 0.5, 0.2, 0.6] }
df = DataFrame(data, index=list("abcd"))
#----------------------
#    count  rate   weapons
# a     23   0.3   shooter
# b      5   0.5   charger
# c     13   0.2  maneuver
# d     21   0.6    roller
#----------------------

df.loc["b", "rate"]
# 0.5

さらに、numpy likeなスライシングも可能である。ただしlocの場合、終わりのラベルを含むデータを取り出すことに注意する。スライシングではなくリストで引数を与えることもできる。

# numpy likeなスライシング、"c"を含むことに注意
df.loc["a":"c", :]
#-------------------------
#    count  rate   weapons
# a     23   0.3   shooter
# b      5   0.5   charger
# c     13   0.2  maneuver
#-------------------------

# リストで列を指定する。この場合は指定した順番で出力が返される。
df.loc["b":"d", ["weapons", "count"]]
#     weapons  count
# b   charger      5
# c  maneuver     13
# d    roller     21

さらに、indexに真理値を用いることもできる。これにより、特定の条件を満たすデータのみを簡単に取り出せる(numpyでもよくやる)。正式になんて呼ぶのかわからんけど、bool maskとかboolian indexingとかいうのかもしれない。

df
#----------------------
#    count  rate   weapons
# a     23   0.3   shooter
# b      5   0.5   charger
# c     13   0.2  maneuver
# d     21   0.6    roller
#----------------------

# 2行目(b)以外を取り出す
df.loc[[True, False, True, True], :]
#-----------------------
#    count  rate   weapons
# a     23   0.3   shooter
# c     13   0.2  maneuver
# d     21   0.6    roller
#-----------------------

# rateが0.4より大きい行のweapons列を取り出す
df.loc[df.loc[:, "rate"] > 0.4, "weapons"]
#-----------------------
# b    charger
# d     roller
# Name: weapons, dtype: object
#-----------------------

ilocを使う

ilocメソッドを使うと、numpyと同じようにインデックスで行と列を指定できる。これが一番わかりやすいかもしれない。

df
#----------------------
#    count  rate   weapons
# a     23   0.3   shooter
# b      5   0.5   charger
# c     13   0.2  maneuver
# d     21   0.6    roller
#----------------------

# 最初の二行と最後の列を取得
df.iloc[:2, -1]
#----------------------
# a    shooter
# b    charger
# Name: weapons, dtype: object
#----------------------

# リストで指定もできる
df.iloc[[3, 1], :]
#----------------------
#    count  rate  weapons
# d     21   0.6   roller
# b      5   0.5  charger
#----------------------

# bool maskも使える
df.iloc[:, [True, True, False]]
#----------------------
#    count  rate
# a     23   0.3
# b      5   0.5
# c     13   0.2
# d     21   0.6
#----------------------

ixを使う

ixメソッドでは、ラベル、インデックス番号どちらでも行と列を指定できる。

df
#----------------------
#    count  rate   weapons
# a     23   0.3   shooter
# b      5   0.5   charger
# c     13   0.2  maneuver
# d     21   0.6    roller
#----------------------

# 行をインデックスで指定、列をラベルで指定
df.ix[:2, "rate"]
#----------------------
# a    0.3
# b    0.5
# Name: rate, dtype: float64
#----------------------

# リスト表記も可能だが、リスト内はラベルorインデックスで統一する
df.ix[[3, 2], "count"]
#----------------------
# d    21
# c    13
# Name: count, dtype: int64
#----------------------

# 統一しないとこうなる
df.ix[[3, "a"], :]
#----------------------
#    count  rate  weapons
# 3    NaN   NaN      NaN
# a   23.0   0.3  shooter
#----------------------

ただし、df.columnsdf.indexのラベルが整数型で指定されていた場合ややこしいことになる。

df.ix[[1, 0], :]

のように書かれた場合、この整数はラベルではなくインデックスとして処理される。 わかってて使うぶんにはいいが、やはり慣れないうちはlocilocを使うべきかもしれない。

以上のことはそのままSeriesにも適用できる(当然だがSeriesは一次元配列的に扱う)。

ソート

単純だけどよく使いそうな機能として、ソートがある。インデックスでソートするsort_indexと値でソートするsort_valuesがある。

sort_index

Seriesに対してインデックスでソートしてみる。

s = Series(range(5), index=list("badce"))
#-----------------
# b    0
# a    1
# d    2
# c    3
# e    4
# dtype: int64
#-----------------

s.sort_index()
#-----------------
# a    1
# b    0
# c    3
# d    2
# e    4
# dtype: int64
#-----------------

DataFrameについても同様であるが、こちらは軸を指定する必要がある。

a = np.arange(12).reshape(3, 4)
df = DataFrame(a, index=list("bac"), columns=list("xzwy"))
#----------------------
#    x  z   w   y
# b  0  1   2   3
# a  4  5   6   7
# c  8  9  10  11
#----------------------

# axisを指定、デフォルトはaxis=0
df.sort_index(axis=1)
#----------------------
#     w  x   y  z
# b   2  0   3  1
# a   6  4   7  5
# c  10  8  11  9
#----------------------

インデックスでソートされているのがわかる。デフォルトでは昇順だが、引数にascending=Falseを与えれば降順になる。リストのソートとの違いとして、オブジェクトのコピーを作るという点がある。リストのソートと同様に扱いたい場合は引数にinplace=Trueを与える。

別の引数によってさらに細かい指定もできる。以下参照
pandas.Series.sort_index — pandas 0.17.0 documentation
pandas.DataFrame.sort_index — pandas 0.21.0 documentation

sort_values

値によってソートする場合はsort_valuesを使う。使い方はsort_indexと同じ。DataFrameに使う場合は引数byを指定することで、どのindex(あるいはcolumn)でソートするかを決定する。

df = DataFrame(np.random.rand(3, 4), index=list("abc"), columns=list("wxyz"))
#--------------------------------------------
#           w         x         y         z
# a  0.179207  0.420959  0.685387  0.787859
# b  0.701033  0.297114  0.346702  0.145790
# c  0.095511  0.724478  0.793895  0.326174
#--------------------------------------------

# b行の値で各行をソート
df.sort_values(axis=1, by="b")
#--------------------------------------------
#           z         x         y         w
# a  0.787859  0.420959  0.685387  0.179207
# b  0.145790  0.297114  0.346702  0.701033
# c  0.326174  0.724478  0.793895  0.095511
#--------------------------------------------

# 二つ指定することもできる
df.sort_values(axis=0, by=["w", "z"])
#--------------------------------------------
#           w         x         y         z
# c  0.095511  0.724478  0.793895  0.326174
# a  0.179207  0.420959  0.685387  0.787859
# b  0.701033  0.297114  0.346702  0.145790
#--------------------------------------------

byに二つ以上指定した場合、最初のラベルでソートされた後、前の結果を崩さない範囲で次のラベルに関してソートが行われる。上の例だと意味ないけど・・・同じ値を取るようなデータの時は有効である。
ラベルが整数の時は整数で指定する。こちらもascendinginplaceの引数を取る。詳しくは以下
pandas.Series.sort_values — pandas 0.21.0 documentation
pandas.DataFrame.sort_values — pandas 0.21.0 documentation

要素の削除

DataFrameから系列を削除するには、組み込みのdelを使うか、dropメソッドを使う。特に理由がなければdropの方が自由度が高いので、こっちを使った方がいい。

df = DataFrame(np.random.rand(3, 4), index=list("abc"), columns=list("wxyz"))
#---------------------------------------------
#           w         x         y         z
# a  0.866489  0.909926  0.685433  0.632792
# b  0.423850  0.708476  0.066990  0.899092
# c  0.811166  0.330274  0.879741  0.640284
#---------------------------------------------

# axisと削除するラベルを指定、リストも使える
df.drop(["w", "z"], axis=1)
#--------------------------
#           x         y
# a  0.909926  0.685433
# b  0.708476  0.066990
# c  0.330274  0.879741
#--------------------------

# Seriesに対しても同様に使える(axisは指定しなくていい)
s = Series(range(5), index=list("abcde"))
s.drop("a")


その他よく使いそうなメソッド

DataFrameから、データに関する様々な統計量を計算できる。pandasを使う理由としてはこの統計量の計算と、データの可視化が非常に簡単にできることが大きいと思う。

numpyよろしく色々な統計量を計算するメソッドが用意されている。例えば - df.sum() 総和 - df.mean() 平均 - df.median() 中央値 - df.cumsum() 累積和 - df.corr() 相関係数 - df.cov() 共分散

などなどである。相関係数や共分散行列を一発で出してくれる。さらに、describeメソッドを使うことで要約統計量を瞬時に計算してくれる。

df = DataFrame(np.random.rand(5, 4), index=list("abcde"), columns=list("wxyz"))
#----------------------------------------------
#           w         x         y         z
# a  0.423700  0.154278  0.969587  0.797716
# b  0.409142  0.115045  0.264065  0.496973
# c  0.560241  0.063190  0.410226  0.740292
# d  0.332259  0.904458  0.438674  0.502878
# e  0.273469  0.744183  0.116515  0.740816
#----------------------------------------------

df.describe()
#----------------------------------------------
#               w         x         y         z
# count  5.000000  5.000000  5.000000  5.000000
# mean   0.399762  0.396231  0.439813  0.655735
# std    0.108306  0.396197  0.322857  0.144151
# min    0.273469  0.063190  0.116515  0.496973
# 25%    0.332259  0.115045  0.264065  0.502878
# 50%    0.409142  0.154278  0.410226  0.740292
# 75%    0.423700  0.744183  0.438674  0.740816
# max    0.560241  0.904458  0.969587  0.797716
#----------------------------------------------

素数、平均、標準偏差などをまとめて見られる。この例では乱数を発生させてるだけなので何の意味もないのだが。。

あとは重複を無視して要素を取り出すuniqueメソッドや、要素数をカウントするvalue_countsも便利そうである。uniqueはnumpyにもある。

s = Series(['c', 'a', 'c', 'a', 'a', 'b', 'b', 'c', 'd'])
s.unique()
# array(['c', 'a', 'b', 'd'], dtype=object)

s.value_counts()
# a    3
# c    3
# b    2
# d    1
# dtype: int64


テキストの読み書き

csvファイルなどを読みこんでDataFrameとして扱うことができる。というか普通はそういう使い方をすると思う。

import pandas as pd
df = pd.read_csv("some.csv")

みたいな使い方をする。csvなので区切り文字はデフォルトで,になっているが、引数sepを与えることで区切り文字を変えられる。正規表現も使える。

以下いろんな引数による使い方

# ヘッダーを含まないファイルを読み込む
# columnsが整数値で適当に割り当てられる
df = pd.read_csv("NonHeader.csv", header=None)

# ヘッダーを含まないファイルに対して、names(columns)を指定して読み込む
# この場合columnsが"a", "b", "c", "d"になる
df = pd.read_csv("NonHeader.csv", names=["a", "b", "c", "d"])

# namesを指定しつつ、特定の列をindexとして読み込む
# この場合"d"列がindexとなる
df = pd.read_csv("NonHeader.csv", names=["a", "b", "c", "d"], index_col="d")      # index_colはintでも指定可

# 特定の行を無視して読み込む
# この場合0, 2, 3行目がスキップされる
df = pd.read_csv("some.csv", skiprows=[0, 2, 3])

全部は列挙しきれないので詳しいことはドキュメントをチェック
pandas.read_csv — pandas 0.21.0 documentation

おわりに

基本的な使い方をひたすら列挙する形になってしまった・・・
これでも全然足りないと思うが、とりあえずなんか触れるくらいには習得できたと思いたい。
可視化周りについてはまた改めて書くかもしれない。