ねこすたっと

ねこの気持ちと統計について悩む筆者の備忘録的ページ。

チュートリアル拾い読み (12):Pandas公式チュートリアル (7/7) [Python]

Rもたいして使いこなせてないのにPythonの勉強を始めてみました。色々と素晴らしいチュートリアルはありそうですが、目移りしてしまうので公式チュートリアルを拾い読みしていきます。

NumPyのあとPandasチートシート(PDF)を中心に読んできましたが、今回で最後です。

前回:

necostat.hatenablog.jp

最終回は「データの要約と可視化」です。Pandas公式チュートリアル拾い読みの初回で紹介した、irisデータを例に使います。

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

データの形状を調べる

.shape()メソッドでdfの形状を表示します。

df.shape  # 出力: (150, 5)

150行5列のデータであることがわかります。

行数を知りたいときは、len()関数を使うこともできます。

len(df)  # 出力: 150

連続変数の要約統計量を計算する

dfに含まれる連続変数の要約値をまとめて調べたいときは.describe()というメソッドを使います。

df.describe()

# 出力: 
       sepal.length  sepal.width  petal.length  petal.width
count    150.000000   150.000000    150.000000   150.000000
mean       5.843333     3.057333      3.758000     1.199333
std        0.828066     0.435866      1.765298     0.762238
min        4.300000     2.000000      1.000000     0.100000
25%        5.100000     2.800000      1.600000     0.300000
50%        5.800000     3.000000      4.350000     1.300000
75%        6.400000     3.300000      5.100000     1.800000
max        7.900000     4.400000      6.900000     2.500000

個々の統計量を得たい場合は次のメソッドを使います。

  • .sum():合計
  • .count():観測個数
  • .median():中央値
  • .quantile([0.25,0.75]):四分位範囲
  • .min():最小値
  • .max():最大値
  • .mean()平均
  • .var():分散
  • .std():標準偏差

カテゴリー変数を要約する

カテゴリー変数に対して.nunique()メソッドを適用すれば、カテゴリー数がわかります。

df["variety"].nunique()  # 出力: 3

個々のカテゴリーに含まれる観察数を求めたい場合は、.value_counts()を使います。

df["variety"].value_counts()

# 出力:
Setosa        50
Versicolor    50
Virginica     50
Name: variety, dtype: int64

グループごとに要約する

例えば治療群など、ある変数のカテゴリー別に要約したいときには、.groupby()メソッドを使ってデータ内のグループ構造を指定します。

グループ分けに使う変数はbyで指定します(インデックス名を使って分けたいときはlevelを使うみたいです)。

df_grouped = df.groupby(by="variety")

各グループ毎に要約する

グループ化されたデータに.describe()メソッドを使うと、各グループ毎の要約が返されます。

df_grouped.describe()

# 出力: 
           sepal.length                                            (略)
                  count   mean       std  min    25%  50%  75%  max ~  
variety                                                               
Setosa             50.0  5.006  0.352490  4.3  4.800  5.0  5.2  5.8 ~  
Versicolor         50.0  5.936  0.516171  4.9  5.600  5.9  6.3  7.0 ~  
Virginica          50.0  6.588  0.635880  4.9  6.225  6.5  6.9  7.9 ~  

各グループの観察数を調べる

グループ化されたデータに.size()メソッドを使うと、各グループのデータ数が返されます。

df_grouped.size()

# 出力: 
variety
Setosa        50
Versicolor    50
Virginica     50
dtype: int64

グループ内の順位を返す

.rank()メソッドを使うと、それぞれのデータの各グループ内での順位が返されます。 主な引数は、以下のとおり。

  • method:同じ値(タイ)の扱い方を指定します。デフォルトはaverageで、タイには平均順位が割り振られます。
  • ascending:デフォルトはTrueで、小さい値に小さい順位が振られます。
  • pctTrueにすると0-1の範囲にスケーリングされます。
df_grouped.rank(method="average")

# 出力: 
     sepal.length  sepal.width  petal.length  petal.width
0            32.5         31.5          18.0         20.0
1            18.5          5.5          18.0         20.0
2            10.5         15.0           8.0         20.0
3             7.5         10.5          31.0         20.0
4            24.5         36.0          18.0         20.0
..            ...          ...           ...          ...
145          31.0         27.5          17.5         40.5
146          16.5          3.5           8.0         19.0
147          26.5         27.5          17.5         24.5
148          12.5         46.5          21.5         40.5
149           7.0         27.5          13.0         11.0

[150 rows x 4 columns]

プロットする

チートシートの扱いはかなり小さく、本格的なグラフ作成をするには(他のライブラリを含め)もう少しじっくりと勉強する必要がありそうです。 ここではチートシートに載っていたヒストグラムと散布図だけにとどめておきます。

.plot()メソッドの中に、グラフの種類を指定すればひとまず描けます。ヒストグラムならkind="hist"だそうです。

df["sepal.length"].plot(kind="hist")

.plotの後に.hist()を繋げても描けるようです。

df["sepal.length"].plot.hist()

散布図はx軸に持ってくる変数とy軸に持ってくる変数を指定する必要があります。

df.plot.scatter(x="sepal.length",y="sepal.width")

おわりに

今回学んだこと:

  • 代表的な要約統計量を計算する
  • グループ毎に計算する
  • 非常に簡単なプロット

チュートリアルは読んだけど取り上げなかったもの:

  • グループ毎に累積和などを計算する方法(.cumsum()など)
  • グループ毎に決まった数だけずらす方法(.shift()

次回:未定

チュートリアル拾い読み (11):Pandas公式チュートリアル (6/7) [Python]

Rもたいして使いこなせてないのにPythonの勉強を始めてみました。色々と素晴らしいチュートリアルはありそうですが、目移りしてしまうので公式チュートリアルを拾い読みしていきます。

NumPyのあとPandasチートシート(PDF)を中心に読んできましたが、そろそろ終盤です。

前回:

necostat.hatenablog.jp

今回はデータの結合です。Pandasライブラリを読み込んでおきます。

import pandas as pd

単純に結合する

pd.concate()という関数を使って結合します。引数としてaxis=0とすれば上下方向に、axis=1とすれば左右方向に結合します(デフォルトはaxis=0)。

上下に結合する

模式的に描くと次のような感じです。

次の2つのサンプルデータ(df1, df2)を上下に結合してみます。2つのdfでは、

  • 変数の並び順が異なっている
  • 重複データが含まれている

となるようにしてみました。

df1 = pd.DataFrame(
    {"patient":["A","B","C"],
     "time1":[10,20,30],
     "time2":[40,50,60]},
)

df2 = pd.DataFrame(
    {"time2":[40,25,35],
     "patient":["A","E","F"],
     "time1":[10,55,65]},
)
pd.concat([df1,df2],axis=0) 

# 出力:
  patient  time1  time2
0       A     10     40
1       B     20     50
2       C     30     60
0       A     10     40
1       E     55     25
2       F     65     35

列の順序が異なっていても列名が揃うように結合してくれています。また、単純に結合するだけなので重複は除かれていません。

左右に結合する

模式的に描くと次のような感じです。

今度は次の2つのサンプルデータ(df1, df3)を左右に結合してみます。2つのdfではpatientの並び順が異なっています。

df1 = pd.DataFrame(
    {"patient":["A","B","C"],
     "time1":[10,20,30],
     "time2":[40,50,60]},
)

df3 = pd.DataFrame(
    {"patient":["C","B","A"],
     "time3":[15,25,35],
     "time4":[45,55,65]},
)

pd.concat()axis=1を指定します。

pd.concat([df1,df3],axis=1) 

# 出力:
  patient  time1  time2 patient  time3  time4
0       A     10     40       C     15     45
1       B     20     50       B     25     55
2       C     30     60       A     35     65

並び順はそのままで結合されています。縦方向のときは列名を元にして対応が決められますが、横方向のときはどれが観測単位を識別する変数か分からないので勝手に揃えてはくれません。

次のようにpatientをインデックス名に指定すれば、

df1_new = df1.set_index("patient")
df3_new = df3.set_index("patient")
pd.concat([df1_new,df3_new],axis=1)

# 出力:
         time1  time2  time3  time4
patient                            
A           10     40     35     65
B           20     50     25     55
C           30     60     15     45

上手く揃えてくれました。でも、このように揃えて結合したいのであれば、次に紹介する方法を使う方が良さそうです。

キーで紐付けて結合する

キーで紐付けして結合する場合は、pd.merge()という関数を使います。指定する主な引数は、

  • how:どの行を残すのか(left, right, inner, outer)を指定します
  • on:紐付けに使うキーを指定します

左側のdfの行を残す

how=leftとすると、左側(1つ目に指定したdf)に含まれる対象者のみ残ります。右側(2つ目に指定したdf)にデータがない箇所はNaNになります。

試しに次の2つのdfを結合してみます。

df1 = pd.DataFrame(
    {"patient":["A","B","C"],
     "time1":[10,20,30],
     "time2":[40,50,60]},
)

df2 = pd.DataFrame(
    {"time2":[40,25,35],
     "patient":["A","E","F"],
     "time1":[10,55,65]},
)

df1が左側、df2が右側として、patientを紐付けキーにして結合します。

pd.merge(df1, df2, how="left", on="patient") 

# 出力:
  patient  time1_x  time2_x  time2_y  time1_y
0       A       10       40     40.0     10.0
1       B       20       50      NaN      NaN
2       C       30       60      NaN      NaN

右側のdfに含まれていない"B", "C"についてはデータが追加されずNaNとなっています。 また、左右で同じ列名がある場合、結合すると列名が重複してしまうので、左側のdfの列名には_x、右側のdfの列名には_yの接尾辞が付与されます。

右側のdfの行を残す

how=rightとすると、右側(2つ目に指定したdf)に含まれる対象者のみ残ります。

指定するdfの順序を入れ替えて、how=leftとしても一緒なので、あまり使う機会がありません。

どちらのdfにも含まれる行を残す

how=innerとすると、左右どちらのdfにも含まれている対象者(積集合)が残ります。

全ての行を残す

how=outerとすると、どちらかのdfに含まれている対象者(和集合)が残ります。

おわりに

今回学んだこと:

  • 2つのデータをキーで紐つけて結合する方法

チュートリアルは読んだけど取り上げなかったもの:

  • 今回もありません(チュートリアルを読む量が少ないだけ)。

次回:

necostat.hatenablog.jp

チュートリアル拾い読み (10):Pandas公式チュートリアル (5/7) [Python]

Rもたいして使いこなせてないのにPythonの勉強を始めてみました。色々と素晴らしいチュートリアルはありそうですが、目移りしてしまうので公式チュートリアルを拾い読みしていきます。

NumPyが終わってPandasに入っています。導入版(Intro to pandas)・簡易版(10 minutes to pandas)は物足りなず、本格版(User guideのIntro to data structures以降)は読みきれなかったので、チートシート(PDF)を中心に読んでいきます。

前回:

necostat.hatenablog.jp

今回はデータの縦横変形です。

横型データ・縦型データとは

同一対象者に対して複数回測定が行われた場合、データを保持する方法が2種類あります。 例えば、A, B, Cの3人に対して1990年と2000年にある項目を測定したデータシートを考えてみます。

横型データは、下のようにそれぞれの人について測定値を横に並べたデータです。よく見かける1行=1対象者の構造になっています。「Wide型」とか「横持ち」などと表現されることもあります。

これに対して縦型データは、それぞれの人について測定値を縦に並べたデータです。「Long型」とか「縦持ち」と表現されることがあります。

縦型データでは、値を格納する変数とその測定の属性(ここでは年)を格納する変数のペアとしてデータを保持します。これに加え、対象者を識別する変数(=ID)があり、同一対象者のデータはこれを元に関連づけられます。横型データが1行=1対象者であったのに対し、縦型データでは1行=1観察単位という構造になっています。

Pandasライブラリを読み込んで、サンプルデータを用意しておきます。

import pandas as pd
df_wide = pd.DataFrame(
    {"patient":["A","B","C"],
     "time1":[10,20,30],
     "time2":[40,50,60]},
)
df_wide

# 出力:
  patient  time1  time2
0       A     10     40
1       B     20     50
2       C     30     60

横型を縦型に変形する

.melt()というメソッドを使います。指定する主な引数は今のとおり。

  • id_vars:縦変換しない変数
  • value_vars:縦型に変換したい変数
  • var_name:ラベルを格納する列につける名前
  • value_name:値を格納する列につける名前
df_long = df_wide.melt(
    id_vars = "patient",
    value_vars = ["time1","time2"],
    var_name = "timing",
    value_name ="lab"
)
df_long

# 出力:
  patient timing  lab
0       A  time1   10
1       B  time1   20
2       C  time1   30
3       A  time2   40
4       B  time2   50
5       C  time2   60

縦型を横型に変形する

.pivot()というメソッドを使って、先程作成したデータ(df_long)を再度横型にしてみます。主な引数は以下のとおり。

  • index:新しいデータフレームのインデックス名に使用する変数
  • columns:横型に展開した部分の列名に使う変数
  • values:横型に展開した部分の値に使う変数
df_rewide = df_long.pivot(
    index = "patient",
    columns = "timing",
    values = "lab"
)
df_rewide

# 出力: 
timing   time1  time2
patient              
A           10     40
B           20     50
C           30     60

インデックスの階層構造を解きたいときは、.reset_index()を使います。

df_rewide.reset_index()

# 出力:
timing patient  time1  time2
0            A     10     40
1            B     20     50
2            C     30     60

おわりに

今回学んだこと:

  • データの縦横変形

チュートリアルは読んだけど取り上げなかったもの:

  • 今回はありません。

次回:

(未作成)

チュートリアル拾い読み (9):Pandas公式チュートリアル (4/7) [Python]

Rもたいして使いこなせてないのにPythonの勉強を始めてみました。色々と素晴らしいチュートリアルはありそうですが、目移りしてしまうので公式チュートリアルを拾い読みしていきます。

NumPyが終わってPandasに入っています。導入版(Intro to pandas)・簡易版(10 minutes to pandas)は物足りなず、本格版(User guideのIntro to data structures以降)は読みきれなかったので、チートシート(PDF)を中心に読んでいきます。

前回:

necostat.hatenablog.jp

前回からデータテーブルの一部の行(= 観察単位, observation)や列(= 変数, variable)、あるいは両者が交差する要素にアクセスする方法を確認しています。今回は列単位の抽出と、ついでに列に対する操作を確認します。

最初にPandasライブラリと、前回使用したirisデータセットを読み込んでおきます。

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

列を抽出する

列名を指定して抽出する

[] の中に列名を書きます。複数の場合はリストで渡します。

df[["sepal.length", "sepal.width"]]

# 出力:
     sepal.length  sepal.width
0             5.1          3.5
1             4.9          3.0
2             4.7          3.2
3             4.6          3.1
4             5.0          3.6
..            ...          ...
145           6.7          3.0
146           6.3          2.5
147           6.5          3.0
148           6.2          3.4
149           5.9          3.0

[150 rows x 2 columns]

条件に合致する列を抽出する

.filter()を使います(Rでは行の部分抽出に使う関数なので間違えそうです)。列の名前に基づいた条件で選んでくることになるので、使いこなそうとすると正規表現(文字列のパターンを表すための決まりごと)を学ぶ必要がありそうです。

df.filter(regex="abc")とすれば、変数名にabcを含んでいるものだけが取り出されます。

例えば、irisデータの中で"al"が含まれるものは、

df.filter(regex="al")
# 出力:
     sepal.length  sepal.width  petal.length  petal.width
0             5.1          3.5           1.4          0.2
1             4.9          3.0           1.4          0.2
2             4.7          3.2           1.3          0.2
3             4.6          3.1           1.5          0.2
4             5.0          3.6           1.4          0.2
..            ...          ...           ...          ...
145           6.7          3.0           5.2          2.3
146           6.3          2.5           5.0          1.9
147           6.5          3.0           5.2          2.0
148           6.2          3.4           5.4          2.3
149           5.9          3.0           5.1          1.8

となります。

その他、頻用しそうなものとして、

  • ^abc:名前がabcで始まるもの
  • xyz$:名前がxyzで終わるもの

は覚えておきます。 もう少し複雑なもの(?!や記号のバックエスケープなど)はまた別の機会に...。

列を除外する

残す変数を選ぶのではなく、削除したい変数を指定する場合は、.drop()を使います。

df.drop("sepal.length", axis=1)

列に対する操作なのでaxis=1が必要です。インデックス名を使って行を削除するときはaxis=0とします。

.drop()は正規表現を直接受け付けてくれないようです。

列名を変更する

.rename()を使います。columns={ }のカッコ内に、"旧名":"新名"の要領で書きます。

df.rename(columns={"sepal.length":"S.L",
                   "sepal.width":"S.W",
                   "petal.length":"P.L",
                   "petal.width":"P.W"})
# 出力:
     S.L  S.W  P.L  P.W    variety
0    5.1  3.5  1.4  0.2     Setosa
1    4.9  3.0  1.4  0.2     Setosa
2    4.7  3.2  1.3  0.2     Setosa
3    4.6  3.1  1.5  0.2     Setosa
4    5.0  3.6  1.4  0.2     Setosa
..   ...  ...  ...  ...        ...
145  6.7  3.0  5.2  2.3  Virginica
146  6.3  2.5  5.0  1.9  Virginica
147  6.5  3.0  5.2  2.0  Virginica
148  6.2  3.4  5.4  2.3  Virginica
149  5.9  3.0  5.1  1.8  Virginica

列を作成する

1つずつ作成する

既存の変数をもとに、新たな変数を作成する場合は、df[]を使います。例えば、sepal.lengthsepal.widthの積を保持するsepal.areaという変数を作成する場合は次のように書きます。

df["sepal.area"] = df["sepal.length"]*df["sepal.width"]
# 出力: 
   sepal.length  sepal.width  petal.length  petal.width variety  sepal.area
0           5.1          3.5           1.4          0.2  Setosa       17.85
1           4.9          3.0           1.4          0.2  Setosa       14.70
2           4.7          3.2           1.3          0.2  Setosa       15.04
3           4.6          3.1           1.5          0.2  Setosa       14.26
4           5.0          3.6           1.4          0.2  Setosa       18.00

複数まとめて作成する

.assign()を使って、複数の列を一度に作成できます。

df = df.assign(sepal_area = df["sepal.length"]*df["sepal.width"],
               petal_area = df["petal.length"]*df["petal.width"])

作成した変数を保持するためにdfに再代入しました。

この方法だとピリオドが入った変数名で作成することができません(引数を辞書形式で渡せば出来るみたいです)。

おわりに

今回学んだこと:

  • 列の抽出・除外
  • 列名変更
  • 列の作成

チュートリアルは読んだけど取り上げなかったもの:

  • 正規表現
  • .assign()メソッドの中でラムダ式で変数を定義する方法

この2つはチートシート拾い読みで扱うには大きすぎます。

  • .clip()を使って指定した上下端でトリミングをする方法
  • .qcut()を使って連続変数をカテゴリー化する方法

この2つは知ってると便利そうだなと思いました。

次回:

necostat.hatenablog.jp

チュートリアル拾い読み (8):Pandas公式チュートリアル (3/7) [Python]

Rもたいして使いこなせてないのにPythonの勉強を始めてみました。色々と素晴らしいチュートリアルはありそうですが、目移りしてしまうので公式チュートリアルを拾い読みしていきます。

NumPyが終わってPandasに入っています。導入版(Intro to pandas)・簡易版(10 minutes to pandas)は物足りなず、本格版(User guideのIntro to data structures以降)は読みきれなかったので、チートシート(PDF)を中心に読んでいきます。

前回:

necostat.hatenablog.jp

前回からデータテーブルの一部の行(= 観察単位, observation)や列(= 変数, variable)、あるいは両者が交差する要素にアクセスする方法を確認しています。今回は行単位の抽出から。

最初にPandasライブラリと、前回使用したirisデータセットを読み込んでおきます。

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

先頭・末尾の行を抽出する

先頭を取り出すには.head()、末尾を取り出すには.tail()というメソッドを使います。取り出したい行数を指定できます。

df.head(n=3)
# 出力:
   sepal.length  sepal.width  petal.length  petal.width variety
0           5.1          3.5           1.4          0.2  Setosa
1           4.9          3.0           1.4          0.2  Setosa
2           4.7          3.2           1.3          0.2  Setosa

ランダムに行を抽出する

.sample()でランダムに行を抽出します。 使う状況はデータが大きすぎるので一部でお試ししたいときなどでしょうか。

抽出する個数か割合のどちらかを指定します。

  • n:抽出する個数を指定
  • frac:元データに対する割合を指定

その他の引数として、

  • random_state:乱数シードを指定
  • replace = True:復元抽出(同じものを2回以上抽出可)をしたいとき

などが指定できます。

df.sample(n=3, random_state=1234)
# 出力:
     sepal.length  sepal.width  petal.length  petal.width     variety
91            6.1          3.0           4.6          1.4  Versicolor
63            6.1          2.9           4.7          1.4  Versicolor
103           6.3          2.9           5.6          1.8   Virginica

指定した列について最大・最小の行を抽出する

.nlargest()で最大の行を、.nsmallest()で最小の行を抽出します。 指定する引数は以下のとおり。

  • n:抽出する個数(割合は不可)
  • columns:対象の列
df.nlargest(n=3, columns="sepal.length")
# 出力:
     sepal.length  sepal.width  petal.length  petal.width    variety
131           7.9          3.8           6.4          2.0  Virginica
117           7.7          3.8           6.7          2.2  Virginica
118           7.7          2.6           6.9          2.3  Virginica

指定した列にもとづいて行を並べ替える

部分抽出ではないですが、.sort_values()を使えば同じことができるのでここで取り上げます。

.sort_values()に大小判定の基準にする変数名を渡します。昇順のときはascending=True、降順のときはascending=Falseとします。

.nlargest()sepal.lengthが大きいもの3つを取り出したいときは、

df.sort_values("sepal.length", ascending=False).head(n=3)

とすれば前述と同じ結果が得られます。

余談ですが、上記のようにメソッドを繋いで書いていけるのは便利ですね。"Method Chaining"と呼ぶみたいです。

条件に合致する行を抽出する

.query()を使います。使い方の注意は次のとおり。

  • カッコの中の条件は、全体を引用符で囲む
  • 変数名にスペースやアンダースコア以外の句読点(.,-,:,^,@など)が含まれているときはバックティック(``)で囲む
df.query("`sepal.length` > 7 & variety != 'Versicolor'")

条件を組み合わせるとき、それぞれの条件をカッコで囲む必要はないみたいですが、カッコをつけておく方が見やすい気がします。

# 出力:
     sepal.length  sepal.width  petal.length  petal.width    variety
102           7.1          3.0           5.9          2.1  Virginica
105           7.6          3.0           6.6          2.1  Virginica
107           7.3          2.9           6.3          1.8  Virginica
109           7.2          3.6           6.1          2.5  Virginica
117           7.7          3.8           6.7          2.2  Virginica
# 以下省略

条件式の中でデータフレーム外の変数を用いたい場合は@を使います。例えば、threshという名前で条件の基準を保持した場合、

thresh = 7
df.query("`sepal.length` > @thresh")

とすれば、sepal.length > 7と同じになります(出力は省略)。

.query()についてはAPIリファレンスを読みました。

pandas.pydata.org

重複している行を除く

.drop_duplicates()を使って、重複している行を除いて一意(unique)な行を抽出します。

df = pd.DataFrame(
    {"a":[10,20,10],
     "b":[40,50,40],
     "c":[70,80,70]},
    index = [1,2,3]
)

1行目と3行目が重複しています。

df.drop_duplicates()

# 出力:
    a   b   c
1  10  40  70
2  20  50  80

重複しているうち、最初に登場する1行目だけが残っています。

おわりに

今回学んだこと:

  • 条件に合致した行の抽出方法
  • Method chainingについて

チュートリアルは読んだけど取り上げなかったもの:

  • 条件式の書き方

次回:

necostat.hatenablog.jp

チュートリアル拾い読み (7):Pandas公式チュートリアル (2/7) [Python]

Rもたいして使いこなせてないのにPythonの勉強を始めてみました。色々と素晴らしいチュートリアルはありそうですが、目移りしてしまうので公式チュートリアルを拾い読みしていきます。

NumPyが終わって前回からはPandasに入っています。導入版(Intro to pandas)・簡易版(10 minutes to pandas)は物足りなず、本格版(User guideのIntro to data structures以降)は読みきれなかったので、チートシート(PDF)を中心に読んでいきます。

前回:

necostat.hatenablog.jp

今回からデータテーブルの一部の行(= 観察単位, observation)や列(= 変数, variable)、あるいは両者が交差する要素にアクセスする方法を確認していきます。

最初にPandasライブラリを読み込んで、サンプルデータを作成しておきます。

import pandas as pd
df = pd.DataFrame(
    {"c1":[10,20,30],
     "c2":[40,50,60],
     "c3":[70,80,90]},
    index = ["r1","r2","r3"]
)
# 出力:
    c1  c2  c3
r1  10  40  70
r2  20  50  80
r3  30  60  90

インデックス名と位置を区別しやすいように、前者は数字ではなく文字列にしました。

ラベル名を使ったアクセス

行の名前(インデックス名)や列の名前(変数名)で部分抽出する方法です。.loc[]を使います。

要素を抽出

.loc[]のカッコの中に、抽出したい行と列の名前を指定します。行名と列名の間は,で区切ります。

df.loc["r1","c1"]  # 出力: 10

複数の行・列を指定したいときは、[ ]を使って名前のリストとして渡します。

df.loc[["r1","r2"], ["c1","c3"]]

# 出力:
    c1  c3
r1  10  70
r2  20  80

複数の行・列は:を使って範囲として指定することができます(スライシングといいます)。ラベル名を使った指定では、開始点も終了点も含まれることに注意です。

df.loc["r1":"r2", "c2":"c3"]

# 出力:
    c2  c3
r1  40  70
r2  50  80

開始点、あるいは終了点だけを指定することもできます。両方指定しなければ「全て」ということになります。

行を抽出

列名を空欄にすれば、指定した行の全ての変数を取り出すことができます。

df.loc["r1",]

# 出力:
c1    10
c2    40
c3    70
Name: r1, dtype: int64

列名を空欄にする代わりに、スライシング(:)で開始・終了を書かないでおいても同じです。

df.loc["r1", :]  # 出力: 省略

列を抽出

行の抽出のときと違って、行名は空欄だとエラーになります。全てを抽出したい場合は:を書かなければいけません(何故なのか、その背景は分かりません)。

df.loc[:,"c2"]

# 出力:
r1    40
r2    50
r3    60

位置番号を使ったアクセス

行や列の位置を番号で指定する方法です。.iloc[]を使います。

要素を抽出

.loc[]のカッコの中に、行の番号・列の番号を指定します。Pythonでは0から数え始めることに注意!

df.iloc[0,0]  # 出力: 10

.iloc[]でスライシングを使うときは終了点を含みません。例えば、

df.iloc[0:2,0]

だと、行番号0, 1のみが抽出され、行番号2(=3行目)は含まれません。

# 出力:
r1    10
r2    20
Name: c1, dtype: int64

行・列を抽出

基本的に.loc[]と同じです。列番号は空欄に出来ますが、行番号は空欄は不可で、:を入れておく必要があります。

おわりに

今回学んだこと:

  • ラベル名と位置番号で部分抽出する方法
  • 上記の両者において、スライシングの終了点の扱いが異なる

チュートリアルは読んだけど取り上げなかったもの:

  • 部分抽出ではなく要素にアクセスする.at[], .iat[]については、同じような使い方であり.loc[], .iloc[]でも代用できると思ったので取り上げませんでした。

次回:

necostat.hatenablog.jp

チュートリアル拾い読み (6):Pandas公式チュートリアル (1/7) [Python]

Rもたいして使いこなせてないのにPythonの勉強を始めてみました。色々と素晴らしいチュートリアルはありそうですが、目移りしてしまうので公式チュートリアルを拾い読みしていきます。

NumPyが終わって今回からはPandasに入りますが、導入版(Intro to pandas)・簡易版(10 minutes to pandas)は物足りなず、本格版(User guideのIntro to data structures以降)は読みきれなかったので、チートシート(PDF)を中心に読んでいきます。

前回:

necostat.hatenablog.jp

Pandasライブラリで扱うもの

Pandasでは表形式のデータを扱います。実際に解析で使うデータシートを読み込んで、整形して、必要なら書き出すというところが中心になります。 それぞれの処理が必要になる状況がNumpyよりも想像しやすいので、モチベーションは保ちやすいですね。

DataFrameとは

PandasではDataFrameという形式の2次元データテーブルを扱います。これは各観察単位(observation)が同じ行(row)に、各変数(variable)が1つの列(column)に整然と並べられて保存されているものです。

チュートリアルではSeriesという1次元データの形式も説明されていますが、実際の作業ではあまり意識することはないので、拾い読みでは割愛します。

DataFrameの作り方

チュートリアルでは色々紹介されていますが、辞書(dictionary)から作る方法と配列(array)から作る方法だけ知っておけば事足りそうです。

辞書から作る

まずPandasライブラリ(ついでにNumpyも)を読み込みます。通常はpdという名前で呼び出します。

import pandas as pd
import numpy as np

Dataframe( )という関数に辞書形式のデータを渡します。このときにインデックスindexも指定しておきます。指定しないと0から始まる連番が振られます。

df = pd.DataFrame(
    {"a":[10,20,30],
     "b":[40,50,60],
     "c":[70,80,90]},
    index = [1,2,3]
)

変数aは10,20,30、変数bは...という要領で、各変数の値を指定していきます。各変数の長さが異なっているとエラーが出ます。

print(df)で確認すると次のとおり。

# 出力:
    a   b   c
1  10  40  70
2  20  50  80
3  30  60  90

配列から作る

今度はDataFrame( )に配列を渡します。列名はcolumnで与えます。

df = pd.DataFrame(
    [[10,40,70],
     [20,50,80],
     [30,60,90]],
    index = [1,2,3],
    columns = ["a","b","c"]
)

確認すると、

# 出力:
    a   b   c
1  10  40  70
2  20  50  80
3  30  60  90

csvデータの読み込み

実際には辞書データや配列データから作る機会はチュートリアル以外にあまりなく、csvファイルとして読み込むことがほとんどです。

例として、あやめの花びら・がく片の長さと幅を含んだirisデータセットを使います。このデータセットはPythonのscikit-learnやseabornといったライブラリに含まれていて、直接呼び出すこともできるようですが、自分のcsvデータを読み込んで解析したいケースが多いと思うので、csvデータをダウンロードしてから読み込みます。

seabornのgithubにcsvデータがありました。

github.com

これをread_csv( )関数を使って読み込みます。上記のコードで、現在プログラムを実行しているディレクトリを確認し、そこにiris.csvという名前でデータを置いているとします。

import os
os.getcwd()
iris = pd.read_csv("iris.csv")

ディレクトリがいまいち分からないけど、とりあえず先に進みたい人は、下のコードでgithubに置いてあるデータを直接読み込むこともできます。

iris = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv")

確認してみましょう

print(iris)
# 出力:
     sepal_length  sepal_width  petal_length  petal_width    species
0             5.1          3.5           1.4          0.2     setosa
1             4.9          3.0           1.4          0.2     setosa
2             4.7          3.2           1.3          0.2     setosa
3             4.6          3.1           1.5          0.2     setosa
4             5.0          3.6           1.4          0.2     setosa
..            ...          ...           ...          ...        ...
145           6.7          3.0           5.2          2.3  virginica
146           6.3          2.5           5.0          1.9  virginica
147           6.5          3.0           5.2          2.0  virginica
148           6.2          3.4           5.4          2.3  virginica
149           5.9          3.0           5.1          1.8  virginica

ちゃんと読み込めているようです。

おわりに

今回学んだこと:

  • 辞書データからDataFrameを作成する
  • 配列データからDataFrameを作成する
  • csvデータをDataFrameとして読み込む

チュートリアルは読んだけど取り上げなかったもの:

  • Seriesに関連したところ
  • Multi-index(階層化されたインデックス)の話

次回:

necostat.hatenablog.jp