(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の集合とみなし、特定の一列だけを取得する。複数列を取得することはできない。また行を指定することもできない。
なので慣れないうちは下で述べるloc
やiloc
で統一した方がわかりやすいと思う。
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.columns
やdf.index
のラベルが整数型で指定されていた場合ややこしいことになる。
df.ix[[1, 0], :]
のように書かれた場合、この整数はラベルではなくインデックスとして処理される。
わかってて使うぶんにはいいが、やはり慣れないうちはloc
かiloc
を使うべきかもしれない。
以上のことはそのまま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
に二つ以上指定した場合、最初のラベルでソートされた後、前の結果を崩さない範囲で次のラベルに関してソートが行われる。上の例だと意味ないけど・・・同じ値を取るようなデータの時は有効である。
ラベルが整数の時は整数で指定する。こちらもascending
とinplace
の引数を取る。詳しくは以下
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
おわりに
基本的な使い方をひたすら列挙する形になってしまった・・・
これでも全然足りないと思うが、とりあえずなんか触れるくらいには習得できたと思いたい。
可視化周りについてはまた改めて書くかもしれない。