1. Pandas
1.1 標本の追加
最初のセルに,
import pandas as pd
# アジア語の文字幅をきれいに表示するおまじない
pd.set_option('display.unicode.east_asian_width', True)
と書いて,実行します.
次に,データフレームを作ります.データフレームには変数(項目名)が必要です.
df = pd.DataFrame(columns=['名前','性別','好きな数','誕生月','セロリ好き度','身長'])
dfを表示するには,普通はprint(df)
としますが,jupyterでは,display(df)
とすると,きれいに表示されます.
今はデータがありませんので,項目名のみが表示されます.
では,データを入れていきましょう.
というか,データが1つでもデータフレームです.
sample_0 = {
'名前':'鈴木', '性別':'女', '好きな数':3,
'誕生月':12, 'セロリ好き度':'好き', '身長':165
} # dictで作ります.
df_0 = pd.DataFrame(data=[sample_0])
display(df_0)
別の標本は,
sample_1 = {
'名前':'薗田', '性別':'男', '好きな数':7,
'誕生月':5, 'セロリ好き度':'とても嫌い', '身長':172
}
df_1 = pd.DataFrame(data=[sample_1])
display(df_1)
これら2つのデータフレームを合成して,新たなデータフレーム df
を作ります.
df = pd.concat([df_0, df_1], ignore_index=True, axis=0)
display(df)
0 |
鈴木 |
女 |
3 |
12 |
好き |
165 |
1 |
薗田 |
男 |
7 |
5 |
とても嫌い |
172 |
axis=1
は,合成の方向です.標本を追加するので縦方向に合成です.縦方向はaxis=0
です.(列方向,項目を追加する場合は,axis=1
)
1.2. 標本セットの読み込み
CSVファイルの読み込みは,read_csv
関数です.
fpath = data_path.joinpath("mydata1.csv") # data_pathはリソースファイルのあるフォルダ(既に設定済)
df = pd.read_csv(fpath, skipinitialspace=True)
display(df)
名前 |
性別 |
好きな数 |
誕生月 |
セロリ好き度 |
身長 |
薗田 |
男 |
7 |
5 |
とても嫌い |
172 |
鈴木 |
女 |
3 |
12 |
好き |
165 |
斎藤 |
男 |
4 |
10 |
嫌い |
180 |
skip-initial-space
はアジア語で余計な空白が入るのを防ぐので,いつもつけておくとよい.
pandas.core.frame.DataFrame
df
はDataFrame
というクラスに割当てられていることがわかります.
df.shape
データフレームの行数(観測数)と列数(変数数)をタプルで表示します.
4つの観測,1つの変数となりました.
あれれ,それは違います.今回のデータは3観測(薗田と鈴木と斎藤),6変数(名前,…, 身長)のはずなので(3,6)
であるはずです.
ファイルを直接見ると,1行目に# mydata1.csv
とあり,これはメモですね.読み飛ばす行番号をリストにしてskiprows
オプションに渡して読み直します.
fpath = data_path.joinpath('mydata1.csv')
df = pd.read_csv(fpath,skipinitialspace=True, skiprows=[0])
df
0 |
薗田 |
男 |
7 |
5 |
とても嫌い |
172 |
1 |
鈴木 |
女 |
3 |
12 |
好き |
165 |
2 |
斎藤 |
男 |
4 |
10 |
嫌い |
180 |
きちんと,3観測6変数になりました.ちなみに読み飛ばした後の最初の行をヘッダ(header)と呼んでおり,変数名のリストとして読みます.
クラス変数 columns
に変数名が保存されています.
ちなみに,ヘッダ行は観測ではないので,(4,6)ではなく(3,6)になっています.
Index(['名前', '性別', '好きな数', '誕生月', 'セロリ好き度', '身長'], dtype='object')
変数名が並んだ行が無いCSVもあります.例えばmydata2.csv
はmydata1.csv
と同じ3観測6変数のデータですが,普通に読むと,
fpath = data_path.joinpath('mydata2.csv')
df = pd.read_csv(fpath,skipinitialspace=True)
df
0 |
鈴木 |
女 |
3 |
12 |
好き |
165 |
1 |
斎藤 |
男 |
4 |
10 |
嫌い |
180 |
のように,データの1観測目がヘッダ(変数名のリスト)と捉えられてしまいます.よって, header
が無いことを教えて read_csv します.( header=None
)
fpath = data_path.joinpath('mydata2.csv')
df = pd.read_csv(fpath,skipinitialspace=True,header=None)
df
0 |
薗田 |
男 |
7 |
5 |
とても嫌い |
172 |
1 |
鈴木 |
女 |
3 |
12 |
好き |
165 |
2 |
斎藤 |
男 |
4 |
10 |
嫌い |
180 |
ただし,ヘッダが無いので変数名が仮に0,….,5となっています.
Index([0, 1, 2, 3, 4, 5], dtype='int64')
きちんと変数名を決めるには このクラス変数columns
を正しいもので上書きします.
df.columns=['Name','Sex','FavoriteNumber','BirthMonth','CeleryFavor','Height']
df
0 |
薗田 |
男 |
7 |
5 |
とても嫌い |
172 |
1 |
鈴木 |
女 |
3 |
12 |
好き |
165 |
2 |
斎藤 |
男 |
4 |
10 |
嫌い |
180 |
変数名を変えるにはrenameメソッドを使ってもよいです.
df.rename(columns={'Sex':'Gender'})
0 |
薗田 |
男 |
7 |
5 |
とても嫌い |
172 |
1 |
鈴木 |
女 |
3 |
12 |
好き |
165 |
2 |
斎藤 |
男 |
4 |
10 |
嫌い |
180 |
ところで,データフレームを表示したときに,左端に毎回0から始まる数字が書かれています. これは,CSVにおける観測の個体IDであり,「インデックス(index)」と呼んでいます.データ本体には含まれません.
RangeIndex(start=0, stop=3, step=1)
今, index
は0から始まって,1ずつ増えて,3の前までということみたいですね.
1.3. データを集めたら
そんなこんなで,mydata1.csv
を読みます.
fpath = data_path.joinpath('mydata1.csv')
df = pd.read_csv(fpath,skipinitialspace=True,header=1)
df
0 |
薗田 |
男 |
7 |
5 |
とても嫌い |
172 |
1 |
鈴木 |
女 |
3 |
12 |
好き |
165 |
2 |
斎藤 |
男 |
4 |
10 |
嫌い |
180 |
1.4. データフレーム上の値の参照
データフレームの参照(抽出)は,2種類あります.参照の出力もデータフレームです.
.loc[index名リスト, 変数名リスト]
: index名リスト,変数名リストによる参照
.iloc[行番号リスト, 列番号リスト]
: データのi
行目リスト,j
列目の指定による参照
取り出されたものの型は DataFrame
いずれの場合も リスト なので,
[1,3,2]
とか["性別",'誕生月']
のように四角カッコの配列で指定します.
# .loc
df.loc[[0, 1], ["好きな数"]]
# .iloc
df.iloc[[0, 1], [2]]
配列要素が数値(数字ではなく数値ね)の場合は,range
でリストを作ることもできます.(内側の四角カッコは無し)
range(start,stop,step)
は [start+0*step, start+1*step, ..., ] < stop
を作るのでした.
df.iloc[range(0, 1), range(2, 5, 2)]
range
で作るものは,スライスと呼ばれる形で表現できます.
1.4.1. ある1つの変数だけ取り出す.
上の例でも1つの変数「好きな数
」を取り出していましたが,全てのサンプルにおけるある1つの変数だけを見たいときがあります.
loc
やiloc
でもいいですが,データフレームになっています.場合によっては値のリストとして取り出したいときがあります.
df['変数名']
: 変数名
の列を取り出す
取り出されたものの型は Series
display(df["好きな数"])
print(df["好きな数"].shape, type(df["好きな数"]))
0 7
1 3
2 4
Name: 好きな数, dtype: int64
(3,) <class 'pandas.core.series.Series'>
表示がきれいではなくなりました.型(クラス)が Series
となっているからです.
Series
は,DataFrame
のある変数を取り出したものになっていて,データ(値のリスト)のサイズが (3,)
つまり,3要素のリストになっています.また,DataFrameと同様に index を持ちます(今の場合,0
, 1
, 2
が index
).さらに Name
に変数名を持ちます.
また,データは普通のリストではなく, 全ての要素が同じ型になっている特別なリスト です(変数というのはそういうもの).今の場合,dtype: int64
つまり 64bit整数 という型です.
Series
はリストと同様に順番のあるコンテナなので,順番を指定することで要素を取り出せます.
サンプル0番目と1番目は,
0 7
1 3
Name: 好きな数, dtype: int64
という感じです.上のほうでも,あるサンプルのある変数の値を取り出しましたが,上のほうではDataFrameを取り出しましたが,今回はSeriesを取り出しています.
またSeries
のままだと扱いにくい場合には,list に変換できます.
1.5. データの要約
要約とは,変数ごとに分布の特徴(統計量)に代表する処理です.
データの要約は describeメソッドで行います.
count |
3.000000 |
3.000000 |
3.000000 |
mean |
4.666667 |
9.000000 |
172.333333 |
std |
2.081666 |
3.605551 |
7.505553 |
min |
3.000000 |
5.000000 |
165.000000 |
25% |
3.500000 |
7.500000 |
168.500000 |
50% |
4.000000 |
10.000000 |
172.000000 |
75% |
5.500000 |
11.000000 |
176.000000 |
max |
7.000000 |
12.000000 |
180.000000 |
値が数値型の変数のみについて要約されました.
count
はデータの個数(sample size,サンプルサイズ)
mean
は平均値,std
は 標本 の標準偏差(STandard Deviation)
min
は最小値,max
は最大値,
25%
, 50%
, 75%
は四分位で,ヒストグラムを低い水準から並べたときに下位25%,50%,75%にあたる水準を答えるものです.下位50%はmedian(メディアン,メジアン)とも呼ばれます.
この関数の中身は,以下のDataFrameのメソッドを使っても表示されます.
# 各変数のサンプルサイズ
df['身長'].count()
# 各変数の標本平均
df['身長'].mean()
# 各変数の標本中央値
df['身長'].median()
# 各変数の標本標準偏差
df['身長'].std(ddof=0)
# 第1四分位
df['身長'].quantile(q=0.25)
# 第2四分位(中央値)
df['身長'].quantile(q=0.5)
# 第3四分位
df['身長'].quantile(q=0.75)
1.6. 変数の尺度を正しく設定
ところで,身長は平均や標準偏差に意味がありますが,「好きな数」や「誕生月」にとっての平均や標準偏差は 意味不明 ですね.
「好きな数」や「誕生月」って,いわば「性別」と同じように,どれが最初でどれが最後だとか無いですね.
また,さきほどの「要約」で,変数「セロリ好き度」は単なる5段階評価値「とても嫌い,嫌い,普通,好き,とても好き」で選んでいるので,代表値や分布を考えられそうですが,文字なので「要約」ができませんでした.
ということで,各変数は,尺度(スケール,scale)を設定しなければいけません.(pandasに教えてあげなければなりません)
名義尺度(nominal) |
分類のための単なる文字・数字 |
== , != , is , not |
順序尺度(ordered) |
順序関係のある文字・数字 |
> , < |
間隔尺度(interval) |
MKS単位系でない測定値 |
+ ,- |
比尺度(ratio) |
MKS単位系の測定値 |
* ,/ |
間隔尺度と比尺度は見分けにくいですが,MKS単位系(メートル,キログラム,秒)を基本単位とする測定値は原器に対する比で表されるので比尺度,それ以外の単位系(または単位無し)の測定値は間隔尺度と考えられます.
では,mydata1.csv
の各変数の尺度は何でしょうか.
また,各変数は,とりうる値のリスト(「水準」といいます)がすでに決められています.各変数の具体的な水準はどうなっているでしょうか.
名前 |
名義尺度 |
あらゆる人名 |
性別 |
名義尺度 |
集合{‘男’,‘女’} |
好きな数 |
名義尺度 |
集合{‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’,‘10’,‘11’,‘12’} |
誕生月 |
名義尺度 |
集合{‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’,‘10’,‘11’,‘12’} |
セロリ好き度 |
順序尺度 |
リスト[‘とても嫌い’,‘嫌い’,‘普通’,‘好き’,‘とても好き’] |
身長 |
比尺度 |
あらゆる実数 |
Pandasでは,それぞれの尺度に型(クラス)を割り当てています.
- 名義尺度は,
Categorical(categories=[...],ordered=False)
クラス
- 順序尺度は,
Categorical(categories=[...],ordered=True)
クラス
- 間隔尺度と比尺度は,
Float
クラス
というわけで,そのように直します.
公式サイトのclass pandas.Categorical
0 薗田
1 鈴木
2 斎藤
Name: 名前, dtype: object
尺度を設定する前のdtypeは,`object`.object型というのは,汎用型(いわば不明)であり,所謂文字列型(str)と同義です.
名義尺度に設定.水準を全て挙げることができないので,`categories` は指定できない.
df['名前'] = pd.Categorical(df['名前'])
df['名前']
0 薗田
1 鈴木
2 斎藤
Name: 名前, dtype: category
Categories (3, object): ['斎藤', '薗田', '鈴木']
dtypeが category になりました.水準は3種です.
0 男
1 女
2 男
Name: 性別, dtype: object
名義尺度に設定.水準がすべて挙げられるなら `categories` に水準の集合を指定する.
GenderSet = {'男','女'}
df['性別'] = pd.Categorical(df['性別'], categories=GenderSet, ordered=False)
df['性別']
0 男
1 女
2 男
Name: 性別, dtype: category
Categories (2, object): ['女', '男']
0 7
1 3
2 4
Name: 好きな数, dtype: int64
名義尺度に設定.
ここで測定した数は,単なる選択肢で名義尺度なので,「数字」(文字)に直す.
dtypeは文字列,`object`型になります.
df['好きな数'] = df['好きな数'].astype('str')
df['好きな数']
0 7
1 3
2 4
Name: 好きな数, dtype: object
また,水準を全て挙げることができないので,`categories`は指定できない.
これで,dtypeは正しく category になります.
df['好きな数'] = pd.Categorical(df['好きな数'], ordered=False)
df['好きな数']
0 7
1 3
2 4
Name: 好きな数, dtype: category
Categories (3, object): ['3', '4', '7']
次に,「誕生月」.尺度を設定する前は,
0 5
1 12
2 10
Name: 誕生月, dtype: int64
これも大小・優劣など無いので,名義尺度であり,文字列化する.
また,全ての水準を挙げられるので,`categories`を指定する.
df["誕生月"] = df["誕生月"].astype("str")
# MonthSet = {'1','2','3','4','5','6','7','8','9','10','11','12'} でもいいですが
MonthSet = set([str(n) for n in range(1, 13)])
df["誕生月"] = pd.Categorical(df["誕生月"], categories=MonthSet, ordered=False)
df["誕生月"]
0 5
1 12
2 10
Name: 誕生月, dtype: category
Categories (12, object): ['5', '7', '2', '12', ..., '4', '11', '6', '8']
0 とても嫌い
1 好き
2 嫌い
Name: セロリ好き度, dtype: object
全ての水準を挙げられるので,`categories` を指定.
また水準には順序があるので,リストに順に水準を並べる.`ordered = True` を指定.
dtype が正しく category になります.また,水準に順位が付いてますね.
CerelyFavorLevel = ['とても嫌い','嫌い','ふつう','好き','とても好き']
df['セロリ好き度'] = pd.Categorical(df['セロリ好き度'],categories=CerelyFavorLevel, ordered=True)
df['セロリ好き度']
0 とても嫌い
1 好き
2 嫌い
Name: セロリ好き度, dtype: category
Categories (5, object): ['とても嫌い' < '嫌い' < 'ふつう' < '好き' < 'とても好き']
あらためて要約しなおし
すべての変数の尺度を設定したので,もう一度「要約」してみましょう.
count |
3.000000 |
mean |
172.333333 |
std |
7.505553 |
min |
165.000000 |
25% |
168.500000 |
50% |
172.000000 |
75% |
176.000000 |
max |
180.000000 |
名義尺度である「好きな数」「誕生月」が消えました.ですが,順序尺度である「セロリ好き度」は依然,出てきません.
値が数値でないと出てこないようです.考えてみると,順序尺度とはいえ,平均値や標準偏差などは数値でしか表示できませんね.水準が文字列なので無理ってことです.
1.7. 文字変数 を 数字 に対応付け
変数が文字列のものは,数字と対応付けすることができます.(se.map(dict)
)
1.7.1. 名義尺度
性別に対して,
に対応付けしたいならば,まず対応を記した辞書を作り,それをマッピングします.
マッピングしたのちも,尺度は変わらず名義尺度(Categorical, ordered=False)となっています.
数字だからといって大小をつけられるわけではありません.
SexMap = {'男':-1, '女':+1}
df['性別'] = df['性別'].map(SexMap)
df['性別']
0 -1
1 1
2 -1
Name: 性別, dtype: category
Categories (2, int64): [1, -1]
1.7.2. 順序尺度
同様に,「セロリ好き度」も数字にしましょうか.
以下のイメージです.
とても嫌い(-2) < 嫌い(-1) < ふつう(0) < 好き(+1) < とても好き(+2)
CerelyFavorMap = {
"とても嫌い": -2,
"嫌い": -1,
"ふつう": 0,
"好き": +1,
"とても好き": +2,
}
df["セロリ好き度"] = df["セロリ好き度"].map(CerelyFavorMap)
df["セロリ好き度"]
0 -2
1 1
2 -1
Name: セロリ好き度, dtype: category
Categories (5, int64): [-2 < -1 < 0 < 1 < 2]
ただし,map
で対応付けしても,文字と数字の対応がされるだけで,数値としては扱われず,大小は評価されません.(dtype: category
)
しかた無いので,順序尺度でなく間隔尺度に設定しなおします. (つまりは数字を数値にする)
df['セロリ好き度'] = df['セロリ好き度'].astype(int)
df['セロリ好き度']
0 -2
1 1
2 -1
Name: セロリ好き度, dtype: int64
あらためて「要約」
あらためて,要約します.
count |
3.000000 |
3.000000 |
mean |
-0.666667 |
172.333333 |
std |
1.527525 |
7.505553 |
min |
-2.000000 |
165.000000 |
25% |
-1.500000 |
168.500000 |
50% |
-1.000000 |
172.000000 |
75% |
0.000000 |
176.000000 |
max |
1.000000 |
180.000000 |
1.8. 名義尺度の再解釈
\(\text{性別}\in\{\text{男性},~\text{女性}\}\) は,名義尺度でした.
ですが,このような2値データの場合,\(\text{男性か?}\in\{\text{Yes},~\text{No}\}\) と解釈し直すこともできます.このような Boolな回答(Yes/No, True/False)は,間隔尺度と考えるのがよいとされています.(名義尺度は計算できないが,間隔尺度は計算できる)
その流れで,2つ以上のカテゴリ水準となる1列の名義尺度は,複数列のBool回答(つまり間隔尺度)に直せます.
例えば
s1 = {"名前": "薗田", "所属学部": "情報データ科学部"}
s2 = {"名前": "鈴木", "所属学部": "工学部"}
s3 = {"名前": "中村", "所属学部": "水産学部"}
df6 = pd.DataFrame([s1, s2, s3])
df6
0 |
薗田 |
情報データ科学部 |
1 |
鈴木 |
工学部 |
2 |
中村 |
水産学部 |
列所属学部
は,名義尺度で,カテゴリは,
categories={"情報データ科学部","工学部","医学部","歯学部","薬学部","環境科学部","水産学部","経済学部","教育学部","多文化社会学部"}
したがって,
df6["所属学部"] = pd.Categorical(df6["所属学部"], categories=categories, ordered=False)
display(df6)
print(df6["所属学部"])
0 |
薗田 |
情報データ科学部 |
1 |
鈴木 |
工学部 |
2 |
中村 |
水産学部 |
0 情報データ科学部
1 工学部
2 水産学部
Name: 所属学部, dtype: category
Categories (10, object): ['環境科学部', '経済学部', '情報データ科学部', '教育学部', ..., '医学部', '水産学部', '工学部', '多文化社会学部']
ここで,「所属学部は?」という変数ではなく,「情報データ科学部に所属?」,「工学部に所属?」,…というように複数のBool変数に分けることができて
df_faculties = pd.get_dummies(df6['所属学部']).astype(int)
display(df_faculties)
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
2 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
のような変数に変更できます.
結果的には,
df7 = pd.concat([df6.drop(columns=["所属学部"]), df_faculties], axis=1)
df7
0 |
薗田 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
鈴木 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
2 |
中村 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
1.9.1. pivot
同じデータですが,場合によっては,次のような表でまとめているかもしれません.
fpath = data_path.joinpath("mydata3.csv")
df2 = pd.read_csv(fpath, skipinitialspace=True, skiprows=[0])
df2
0 |
薗田 |
性別 |
男 |
1 |
薗田 |
好きな数 |
7 |
2 |
薗田 |
誕生月 |
5 |
3 |
薗田 |
セロリ好き度 |
とても嫌い |
4 |
薗田 |
身長 |
172 |
5 |
鈴木 |
性別 |
女 |
6 |
鈴木 |
好きな数 |
3 |
7 |
鈴木 |
誕生月 |
12 |
8 |
鈴木 |
セロリ好き度 |
好き |
9 |
鈴木 |
身長 |
165 |
10 |
斎藤 |
性別 |
男 |
11 |
斎藤 |
好きな数 |
4 |
12 |
斎藤 |
誕生月 |
10 |
13 |
斎藤 |
セロリ好き度 |
嫌い |
14 |
斎藤 |
身長 |
180 |
この「回答」という列ですが,尺度は何でしょうか?
あるときは「男」や「とても嫌い」などの文字列,あるときは「7」や「172」などの数字になっていて,なんとも言えません.
このような尺度を決めきれないような列のある表は「雑然」としています.よって「整然」データに変換します.
よく見ると「変数名」という列の値の種別ごとに表を分けると,
というように,それぞれの表で回答の列がある尺度や分布に統一できそうです.
むしろ,「回答」という列名ではなく,それぞれの表の名前(つまり元の変数名の値)を列名にすればよいのではないでしょうか.
この作業を ピボット といいます.
pandas
では,pivot
関数を使います.変数名が並ぶ列と,回答が並ぶ列を指定し,それ以外の列をindexに指定すると,変換できます.
df3 = pd.pivot(df2, columns="変数名", values="回答", index=["名前"])
df3
名前 |
|
|
|
|
|
斎藤 |
嫌い |
4 |
男 |
10 |
180 |
薗田 |
とても嫌い |
7 |
男 |
5 |
172 |
鈴木 |
好き |
3 |
女 |
12 |
165 |
1.9.2. from wide to long (melt)
こんなデータがあったとします.
Code
import pandas as pd
df_univ = pd.DataFrame(
{
"ID": ["鈴木", "鈴木", "鈴木", "中村", "中村", "中村", "高橋", "高橋", "高橋"],
"Q": [
"志望大学",
"入学大学",
"出身",
"志望大学",
"入学大学",
"出身",
"志望大学",
"入学大学",
"出身",
],
"A": ["長大", "長大", "長崎", "東大", "長大", "東京", "長大", "京大", "長崎"],
}
)
df_univ
0 |
鈴木 |
志望大学 |
長大 |
1 |
鈴木 |
入学大学 |
長大 |
2 |
鈴木 |
出身 |
長崎 |
3 |
中村 |
志望大学 |
東大 |
4 |
中村 |
入学大学 |
長大 |
5 |
中村 |
出身 |
東京 |
6 |
高橋 |
志望大学 |
長大 |
7 |
高橋 |
入学大学 |
京大 |
8 |
高橋 |
出身 |
長崎 |
A列の尺度がバラバラで,Qによって分けると良さそうです.
df_univ = df_univ.pivot(columns="Q", values="A", index="ID")
df_univ = df_univ.reset_index(["ID"])
df_univ
0 |
中村 |
長大 |
東京 |
東大 |
1 |
鈴木 |
長大 |
長崎 |
長大 |
2 |
高橋 |
京大 |
長崎 |
長大 |
志望大学
列と入学大学
列は,尺度も分布も同一なので,これらの列名は,変数名ではなく値と考えられます.(wide型です.)
long型に直します.wide型(同一尺度・分布の列が複数ある表)をlong型(同一尺度・分布の列が1列)にするには,melt
関数を使います.
df_univ = df_univ.melt(id_vars=["ID", "出身"])
df_univ
0 |
中村 |
東京 |
入学大学 |
長大 |
1 |
鈴木 |
長崎 |
入学大学 |
長大 |
2 |
高橋 |
長崎 |
入学大学 |
京大 |
3 |
中村 |
東京 |
志望大学 |
東大 |
4 |
鈴木 |
長崎 |
志望大学 |
長大 |
5 |
高橋 |
長崎 |
志望大学 |
長大 |
列名に適切な名前をつける
df_univ.columns = ["ID", "出身", "入学と志望", "大学名"]
df_univ
0 |
中村 |
東京 |
入学大学 |
長大 |
1 |
鈴木 |
長崎 |
入学大学 |
長大 |
2 |
高橋 |
長崎 |
入学大学 |
京大 |
3 |
中村 |
東京 |
志望大学 |
東大 |
4 |
鈴木 |
長崎 |
志望大学 |
長大 |
5 |
高橋 |
長崎 |
志望大学 |
長大 |
1.10. まとめ
- データフレームを作る
- ファイルから読み込む:
pd.read_csv(csvファイル名, header=)
- 手打ちで入力:
pd.DataFrame(data=[dict], columns=[])
- 標本を付け足す:
pd.concat([df0,df1], ignore_index=True, axis=0)
- 整然化
- ある列の尺度や分布が複数混ざり合っていて,別の列の値(カテゴリ)によって表を分けたほうが良さそうなら,その列の値(カテゴリ)が実は変数である.
- -> ピボット:
pd.pivot(df,columns=var_col, values=var_val, index=[var_id0, var_id1,...])
- 尺度や分布が同一の列が複数ある(wide型)なら,それらの列名は実は値である.
- -> メルト:
pd.melt(df, id_vars=[var_id0, var_id1, ...])
- 尺度を設定
- 質的変数(文字,数字):
- 名義尺度:
se = pd.Categorical(se, categories=[...], ordered=False)
- 順序尺度:
se = pd.Categorical(se, categories=[...], ordered=True)
- 量的変数(数値):
- 間隔尺度・比尺度:
se = se.astype('float')
1.11. クロス集計
クロス集計表を作ることもできます.クロスさせる2つの変数名を引数に並べます.
pd.crosstab(df['性別'],df['好きな数'])
2. 課題k02
これは,身近の14人に性別と身長を尋ねたときの回答を集めた標本データである.k02/k02/data/heights14.csv
とする.
from importlib import resources
from k02 import data
data_path = resources.files(data)
fpath = data_path.joinpath("heights14.csv")
df = pd.read_csv(fpath)
df
0 |
1 |
性別 |
男 |
1 |
1 |
身長 |
183.97 |
2 |
2 |
性別 |
男 |
3 |
2 |
身長 |
179.54 |
4 |
3 |
性別 |
女 |
5 |
3 |
身長 |
166.9 |
6 |
4 |
性別 |
男 |
7 |
4 |
身長 |
173.62 |
8 |
5 |
性別 |
女 |
9 |
5 |
身長 |
165.62 |
10 |
6 |
性別 |
男 |
11 |
6 |
身長 |
167.83 |
12 |
7 |
性別 |
女 |
13 |
7 |
身長 |
152.4 |
14 |
8 |
性別 |
女 |
15 |
8 |
身長 |
163.24 |
16 |
9 |
性別 |
女 |
17 |
9 |
身長 |
161.39 |
18 |
10 |
性別 |
男 |
19 |
10 |
身長 |
174.38 |
20 |
11 |
性別 |
男 |
21 |
11 |
身長 |
171.38 |
22 |
12 |
性別 |
女 |
23 |
12 |
身長 |
152.28 |
24 |
13 |
性別 |
男 |
25 |
13 |
身長 |
169.39 |
26 |
14 |
性別 |
男 |
27 |
14 |
身長 |
171.1 |
で読み取れます.ヘッダー読み取りの有無等は考えてください.
また,このデータは,尺度が混ざっている列があるので,ピボットが必要です.
# columnsを変数名が並んでる列名,今回は "Q"
# valuesを各変数の値が並んでる列名,今回は "A"
# indexを固定する列名のリスト,今回は ["ID"]
# としてピボット
df2 = df.pivot(columns="Q", values="A", index=["ID"])
display(df2)
print(df2.dtypes)
ID |
|
|
1 |
男 |
183.97 |
2 |
男 |
179.54 |
3 |
女 |
166.9 |
4 |
男 |
173.62 |
5 |
女 |
165.62 |
6 |
男 |
167.83 |
7 |
女 |
152.4 |
8 |
女 |
163.24 |
9 |
女 |
161.39 |
10 |
男 |
174.38 |
11 |
男 |
171.38 |
12 |
女 |
152.28 |
13 |
男 |
169.39 |
14 |
男 |
171.1 |
Q
性別 object
身長 object
dtype: object
pivotされましたが,変数のtypeがobject
(なにがし)になっています. このままだと,統計量が正しく計算できません(特に身長), よって,次に尺度を正します.
変数 性別
は,
なので,category
に直します.categories={'男','女'}
, ordered=False
です.
変数 身長
は,
なので,float
に直します.
df2['性別'] = pd.Categorical(df2['性別'], categories={'男','女'}, ordered=False)
df2['身長'] = df2['身長'].astype(float)
display(df2)
print(df2.dtypes)
ID |
|
|
1 |
男 |
183.97 |
2 |
男 |
179.54 |
3 |
女 |
166.90 |
4 |
男 |
173.62 |
5 |
女 |
165.62 |
6 |
男 |
167.83 |
7 |
女 |
152.40 |
8 |
女 |
163.24 |
9 |
女 |
161.39 |
10 |
男 |
174.38 |
11 |
男 |
171.38 |
12 |
女 |
152.28 |
13 |
男 |
169.39 |
14 |
男 |
171.10 |
Q
性別 category
身長 float64
dtype: object
きちんと尺度が正されました.
k02_1
pandasパッケージを用いて,csvファイルを読み込んでデータフレームを作成し, この標本集団における男性と女性それぞれの「平均」と「標準偏差」を表示する関数を k02_1.py
の中で main()
として定義せよ.
開発手順は,
k02/k02_1.ipynb
を作り,Jupyter形式で,入力セルと出力セルを確認しながらプログラミング
k02/k02_1.ipynb
を k02/k02_1.py
にエクスポートする.入力セルのみが .py にコピーされる.
- jupyter形式ではないため
display()
は使えないので消す.
poetry run python k02/k02_1.py
で確認.
k02_1.py
をモジュールに書き換える.
- import行のあとのすべての処理を
def main():
ブロックに入れる.
k02_1.py
の末尾に,if __name__ == '__main__': main()
という行を追加
- 動作確認用に
tests/test_k02_1.py
を書いて,正しく動作することを確認
from k02 import k02_1
k02_1.main()
k02_1.py
を以下のように,def main()
に処理,if __name__ == '__main__': main()
を末尾に設定,とすると,
import pandas as pd
def main():
# ここに処理を書く(インデントに気をつけよ)
if __name__ == '__main__': main()
> poetry run python k02/k02_1.py
は,
> poetry run python
>>> from k02 import k02_1
>>> k02_1.main()
実行結果は
==男==
平均:173.90 cm
標準偏差: 5.06 cm
==女==
平均: ◯ cm
標準偏差: ◯ cm
と表示されるようにせよ.(男について表示される数値は上記になるのが正解)
ちなみに,標本分散は var(ddof=0)
,標本標準偏差は, std(ddof=0)
で計算される.(いくつかのサイトで誤っていることを確認しているので,注意すること)
df_M = df2[df2["性別"] == "男"]
display(df_M)
se_M_height = df_M["身長"]
display(se_M_height)
M_std = se_M_height.std(ddof=0) # 標準偏差
M_ustd = se_M_height.std(ddof=1) # 不偏標準偏差
print(f"標準偏差:{M_std}\n不偏標準偏差:{M_ustd}")
M_var = se_M_height.var(ddof=0) # 分散
M_uvar = se_M_height.var(ddof=1) # 不偏分散
print(f"分散:{M_var}\n不偏分散:{M_uvar}")
ID |
|
|
1 |
男 |
183.97 |
2 |
男 |
179.54 |
4 |
男 |
173.62 |
6 |
男 |
167.83 |
10 |
男 |
174.38 |
11 |
男 |
171.38 |
13 |
男 |
169.39 |
14 |
男 |
171.10 |
ID
1 183.97
2 179.54
4 173.62
6 167.83
10 174.38
11 171.38
13 169.39
14 171.10
Name: 身長, dtype: float64
標準偏差:5.060863161309539
不偏標準偏差:5.410290294561703
分散:25.612335937499985
不偏分散:29.271241071428555
k02_2
このデータはある特定の14人のデータなので,別な14人で回答を集めるたびに別の標本平均が求まる.
性別それぞれ同じ人数で回答を集めたときにその標本平均が68%の確率で収まる範囲「◯±△」を推定せよ.(\(\bar{x}\pm 1\times \mathrm{SE}\))
k02_1
と同様に最終的には モジュールk02/k02_2.py
を作り,tests/test_k02_2.py
で動作確認せよ.
- 「◯±△」の「◯」は,母集団の平均の裁量推定値と等しく,手元の標本の平均(\(\bar{x}\))と等しい.
- また,「◯±△」の「△」は,別の標本と手元の標本との誤差であり,標本誤差\(\mathrm{SE}\)という. \(\sqrt{\frac{u^2}{N}}\) で求まる.
ちなみに,母集団の分散と標準偏差は,var(ddof=1)
,std(ddof=1)
で計算される.(いくつかのサイトで誤っていることを確認しているので,注意すること)
実行結果は,
==男==
別の標本の平均の68%信頼区間:173.90± 1.91 cm
==女==
別の標本の平均の68%信頼区間: ◯ ± △ cm
と表示されるようにせよ.(男について表示される数値は上記になるのが正解)
ちなみに,今回は各値を小数点以下2桁までを表示している.x
を小数点以下2桁までで表示する場合には,fフォーマットで文字列を作ります.fフォーマットでは,中括弧{x}
でxを文字列化します.また,:
のあとに,表記方法を指定します.
x = 3.141592
print(f"円周率は {x} です(通常表記).")
print(f"円周率は {x:.2f} です(小数点以下2桁表示).")
print(f"円周率は {x:.3e} です(小数点以下3桁の指数表示,つまり有効桁4桁).")
print(f"円周率は {x:.5g} です(有効桁5桁).")
円周率は 3.141592 です(通常表記).
円周率は 3.14 です(小数点以下2桁表示).
円周率は 3.142e+00 です(小数点以下3桁の指数表示,つまり有効桁4桁).
円周率は 3.1416 です(有効桁5桁).