元記事
Python学習記録_プログラミングガチ初心者がKaggle参加を目指す日記
8日目です。
今日はPandasの応用ということでここでも実用的なものがありそうで楽しみですね。
…というか気づけば残すところ今日と明日で最低限必要な知識が全て身につく(はず)みたいですが本当にこのレベルでKaggle参加できるのかちょっと不安ではあります。
まあ結局のところ実際にやってみないと分からないことだらけだと思うのでとりあえずはデータに触ってみる、というのができるようになることを目標に今日も頑張っていきましょう。
関数 中級(zip,map,filter) 25m
名前付き引数の指定
Pythonは引数を指定する時に、引数の名前を指定して関数を呼び出すことが出来ます。
このことを、名前付き引数やキーワード引数と呼んだりします。
def sample(weight, height):
print("体重は"+str(weight) + "kg")
print("身長は"+str(height) + "cm")
sample(50, 170)
sample(170,50)
体重は50kg
身長は170cm
体重は170kg
身長は50cm
こんな形で本来第1引数には体重を渡さないといけないのに身長を渡してしまう、みたいなミスが起きるけど
名前付きで引数を指定してあげることでそういうミスを防ぐことができるとのこと。
def sample(weight, height):
print("体重は"+str(weight) + "kg")
print("身長は"+str(height) + "cm")
sample(weight=50, height=170)
sample(height=170,weight=50)
体重は50kg
身長は170cm
体重は50kg
身長は170cm
zip()
zip()関数を使うことで、2つ以上(3つや4つでも可)のリストやタプルを、要素がタプルのリストにまとめることが出来る組み込み関数です。
a = [1, 2, 3]
b = [4, 5, 6]
z = zip(a, b)
print(list(z))
[(1, 4), (2, 5), (3, 6)]
これはなぜprint(z)じゃだめなんだ…?
zipでまとめるだけだとリスト型になってない、みたいな理由かな。
forを用いることで、ループ中に複数のリストの要素を取得することが出来ます。
リストやタプルなどの要素を同時に取得したい場合に、forとzip()を用いる事で可能です。
questions = ["name", "favorite food"]
answers = ["Tani", "pasta"]
for q, a in zip(questions, answers):
print("What is your {0}? It is {1}.".format(q,a))
What is your name? It is Tani.
What is your favorite food? It is pasta.
なるほど…タプルの形で2つのリストを1つにまとめることで良い感じにfor文に組み込むことができると…
引数は2つだけでなく3つ、4つも可能で
a = [1, 2, 3]
b = [2, 4, 6]
c = [3, 4, 5]
for x, y, z in zip(a, b, c):
print(x, y, z)
1 2 3
2 4 4
3 6 5
こんな使い方もできると。
うーむ…難しい…
map()
map()関数を使うことで、リストの要素に演算を適用してくれます。
第一引数に関数を渡し、第2引数にシーケンスと呼ばれる、複数ある値をひとかたまりとして格納した値を渡します。
def square(x):
return x * x
li = list(map(square, range(1, 10)))
print(li)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
コレはfor文でも代替できそうな気がしますね。
li=[]
for i in range(1,10):
li.append(i**2)
print(li)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
うーん…便利な気もするけど今のところはforでやることに比べてのメリットが思いつかないな…
関数が複雑になってきたらこっちの方が良いのかな、くらいのイメージですが
きっとこれも実際に使っていくと利便性に気づけるものなんでしょう、うん。
filter()
filter関数は、引数を2つ受け取り、第一引数には関数を、第二引数に渡したデータを渡し、Trueとなる要素だけからなるデータを返します。
filter関数は、ある値より大きい値だけを取り出したい場合や、特定の文字列を含む要素を取り出したい場合に利用します。
def is_mod(x):
return x % 3 == 1
li = list(filter(is_mod, range(1 ,10)))
print(li)
[1, 4, 7]
これもforとifを使えば代用できそうな…
li = []
for i in range(1,10):
if i % 3==1:
li.append(i)
print(li)
[1, 4, 7]
mapと同じ気配を感じます。
再帰関数
再帰関数とは、自分自身を呼び出す関数のことです。
def factrial(i):
if i == 0:
return 1
else:
return i*factrial(i-1)
print(factrial(6))
factrialという関数の中でfactrialの関数が使われているという不思議な感じですね。
これはどこかで制限かけないと無限ループになりそうです。
Pandas応用 45m
データのイテレーション
Pandasではデータを列や表形式のデータフレームなどを使いますが、これらを何らかの法則で値を取得し、操作をすることが求められます。
Pandasで扱えるデータ構造としては、Series(1列のみのデータ型)、DataFrame(2次元のラベル付きデータ)について、「Pandas入門」の章で取り上げました。
さらに、イテレーション方法の一つにGroupByというデータ構造があります。
GroupByは指定したカラムの値によって、DataFrameに入ったデータをグループ分けすることで実現します。
イテレーション とは 検索←ポチッ
Python pandas データのイテレーションと関数適用、pipe
pandas ではデータを 列 や 表形式のデータ構造として扱うが、これらのデータから順番に値を取得 (イテレーション) して何か操作をしたい / また 何らかの関数を適用したい、ということがよくある。
複数のデータに対して順番に値を取得すること=イテレーションという認識で良いのかな…?
そしてGROUP BYがでてきました。
SQL初めてやった時はグループ化の概念が全然理解できなくてハゲあがりましたが
pythonでも考え方は同じ感じなんでしょうか
import pandas as pd
import numpy as np
df = pd.DataFrame({
'苗字' : ['田中','田中','山田', '高橋'],
'名前' : ['太郎','花子','次郎','三郎'],
'役割' : ['営業部長', '広報部', '技術責任者','平社員'],
'身長' : [178,173,169,180]
})
print(df)
grouped = df.groupby('苗字')
for name, group in grouped:
print(name)
print(group)
苗字 名前 役割 身長
0 田中 太郎 営業部長 178
1 田中 花子 広報部 173
2 山田 次郎 技術責任者 169
3 高橋 三郎 平社員 180
山田
苗字 名前 役割 身長
2 山田 次郎 技術責任者 169
田中
苗字 名前 役割 身長
0 田中 太郎 営業部長 178
1 田中 花子 広報部 173
高橋
苗字 名前 役割 身長
3 高橋 三郎 平社員 180
こんな感じでGROUPBYで指定したカラムが同じものをなるべくまとめようとするって感じですね、SQLに近い。
…ってことはこれとCOUNTとかSUMとかを掛け合わせて集計ができそうな気配です。
map
Pandasでデータを扱う上で、Series, DataFrame, GroupByのデータ構造がありますが、それぞれにおいて適した関数が数種類あります。
Seriesの各値に対して、関数を適用する方法は主に、Series.mapとSeries.applyです。
実装方法としては、以下のような方法が挙げられます。
要素(スカラー値)に対する関数として、Seriesの各要素に適用する場合は、map()を使用します。
先ほど学んだmapとはまた別物なのか…?
リストに使えるってことはシリーズにも使えるような気がするんですがどうでしょう。
import pandas as pd
d = pd.Series([1,3,5,10], index=["A","B","C","D"])
d_plus2_apply = d.apply(lambda x: x + 2)
print(d_plus2_apply)
print(d.map(lambda x: x * 2))
d_DataFrame = d.apply(lambda x: pd.Series([x, x * 2], index=['col1', 'col2']))
print(d_DataFrame)
要するにmapもapplyも挙動は変わらない、ってことですかね。
データフレームでも使えるなら基本apply一択で使えばいいんじゃないの…?
apply
DataFrameにはDataFrame.apply()とDataFrame.applymap関数が適用出来ます。
apply()は行・列に対する関数として、DataFrame,Seriesの各行・各列に適用する場合にapply()を使用します。
import pandas as pd
import numpy as np
df = pd.DataFrame({
'役割' : ['営業', 'エンジニア', '営業', 'エンジニア'],
'体重' : [65, 60, 55, 72],
'身長' : [178, 173, 169, 183]
})
grouped = df.groupby('役割')
print(grouped["身長"].apply(np.mean))
役割
エンジニア 178.0
営業 173.5
Name: 身長, dtype: float64
うん…全部applyで良さそうな気しかしない…
階層型インデックス(Hierarchial / Multi Index)
階層型インデックスとは、DataFrameなどで、複数の列をインデックスとして持たせる機能です。
なので、データを一意に決定するには複数のインデックスを指定する必要があります。
まず、階層型インデックスが必要なデータの特徴は、次の通りです。
・ユニークな列がなく、どれが実測値か分からないデータ
・いくつかのインデックスを指定して初めて実測値が一意に定まる
import pandas as pd
import numpy as np
df1=pd.read_csv('/content/MultiIndex.csv')
print(df1)
name year product expected actual
0 Ojima-Caffe 2015 coffee 682 757
1 Ojima-Caffe 2015 tea 597 485
2 Ojima-Caffe 2016 coffee 692 379
3 Ojima-Caffe 2016 tea 575 308
4 Ojima-Caffe 2017 coffee 508 597
5 Ojima-Caffe 2017 tea 597 657
6 Song-cafe 2015 coffee 603 651
7 Song-cafe 2015 tea 391 483
8 Song-cafe 2016 coffee 533 530
9 Song-cafe 2016 tea 552 638
10 Song-cafe 2017 coffee 793 732
11 Song-cafe 2017 tea 642 407
例えばこんなデータを扱う際に、Ojima-Caffeの2015年のcoffeeの売上が欲しい時には
import pandas as pd
import numpy as np
df1=pd.read_csv('/content/MultiIndex.csv')
print(df1)
print(df1[(df1["name"] == "Ojima-Caffe")&(df1["year"] == 2015)&(df1["product"] == "coffee")].iloc[0])
こんな形で店舗名・年度・商品名のすべてを指定しないといけないのでスクリプトが長くなってしまう
これを階層型インデックスとして指定してあげると
import pandas as pd
import numpy as np
Multi_df = pd.read_csv('/content/MultiIndex.csv',index_col=["name","year","product"])
print(Multi_df)
print(Multi_df.loc[("Ojima-Caffe",2015,"coffee")])
print(Multi_df.loc[("Ojima-Caffe")])
print(Multi_df.loc[("Ojima-Caffe",2015)])
expected actual
name year product
Ojima-Caffe 2015 coffee 682 757
tea 597 485
2016 coffee 692 379
tea 575 308
2017 coffee 508 597
tea 597 657
Song-cafe 2015 coffee 603 651
tea 391 483
2016 coffee 533 530
tea 552 638
2017 coffee 793 732
tea 642 407
expected 682
actual 757
Name: (Ojima-Caffe, 2015, coffee), dtype: int64
expected actual
year product
2015 coffee 682 757
tea 597 485
2016 coffee 692 379
tea 575 308
2017 coffee 508 597
tea 597 657
expected actual
product
coffee 682 757
tea 597 485
タプルを渡すだけでデータの選択ができるようになるし
インデックスの指定の仕方にも自由度が増すという形になると。
主キーとしてindex番号振る代わりにカラムの中身を左からグループ化してインデックス代わりにしてる感じですね、たぶん…
データフレームの列追加
作成済みのデータフレームに新しい列名を追加し、それらのデータを記入することで、列の追加ができます。
追加するデータは Python のリストや numpy の行列 (np.array) を指定できます。
import pandas as pd
import numpy as np
Multi_df = pd.read_csv('/content/MultiIndex.csv',index_col=["name","year","product"])
Multi_df['price'] = np.array([320, 210, 310, 180, 315, 170,
300, 240, 310, 220, 330, 220])
print(Multi_df)
def total_expected_def(series):
return series['expected'] * series['price']
def total_actual_def(series):
return series['actual'] * series['price']
Multi_df['total_expected'] = Multi_df.apply(total_expected_def,axis=1)
Multi_df['total_actual'] = Multi_df.apply(total_actual_def,axis=1)
print(Multi_df)
expected actual price
name year product
Ojima-Caffe 2015 coffee 682 757 320
tea 597 485 210
2016 coffee 692 379 310
tea 575 308 180
2017 coffee 508 597 315
tea 597 657 170
Song-cafe 2015 coffee 603 651 300
tea 391 483 240
2016 coffee 533 530 310
tea 552 638 220
2017 coffee 793 732 330
tea 642 407 220
expected actual price total_expected
name year product
Ojima-Caffe 2015 coffee 682 757 320 218240
tea 597 485 210 125370
2016 coffee 692 379 310 214520
tea 575 308 180 103500
2017 coffee 508 597 315 160020
tea 597 657 170 101490
Song-cafe 2015 coffee 603 651 300 180900
tea 391 483 240 93840
2016 coffee 533 530 310 165230
tea 552 638 220 121440
2017 coffee 793 732 330 261690
tea 642 407 220 141240
total_actual
name year product
Ojima-Caffe 2015 coffee 242240
tea 101850
2016 coffee 117490
tea 55440
2017 coffee 188055
tea 111690
Song-cafe 2015 coffee 195300
tea 115920
2016 coffee 164300
tea 140360
2017 coffee 241560
tea 89540
これは6日目に躓いたところですね。
あの時はapplyを使わなかった気がしますがメソッド使って新しい値を作成する時はapplyが必要だからかなという認識。
describe
Describeメソッドは迅速かつ、簡単にすべてのデータから統計量を算出します。
統計量としては、データの数(count)、平均値(mean)、std(標準偏差)、最小値(min)、最大値(max)、四分位数(25%,50%,75%)などがあります。
import pandas as pd
import numpy as np
Multi_df = pd.read_csv('/content/MultiIndex.csv',index_col=["name","year","product"])
print(Multi_df.describe())
expected actual
count 12.000000 12.000000
mean 597.083333 552.000000
std 101.662953 142.986331
min 391.000000 308.000000
25% 547.250000 464.000000
50% 597.000000 563.500000
75% 652.000000 652.500000
max 793.000000 757.000000
これはデータの前処理が終わってEDAの段階に入ったらとりあえず初手で見るものなイメージ。
corr
corr(相関/correlation)メソッドは、2つの列を入力し、相関行列を出力します。
予想したデータ('expected’)と実際のデータ('actual’)における相関行列は以下のように求めることが出来ます。
相関係数の絶対値が高ければ高いだけ、データの間に相関があるといえます。
import pandas as pd
import numpy as np
Multi_df = pd.read_csv('/content/MultiIndex.csv',index_col=["name","year","product"])
print(Multi_df.corr())
expected actual
expected 1.000000 0.271756
actual 0.271756 1.000000
データの結合(merge関数)
Pandasのmergeを用いることでデータを結合する事が可能です。
import pandas as pd
from pandas import DataFrame, Series
df = DataFrame({
'名前' :['田中','山田','高橋','金沢','加藤'],
'役割' :['営業','エンジニア','広報','営業','エンジニア'],
'身長' :[178, 173, 169, 165, 174]
})
other = DataFrame({
'名前' :['田中','金沢',],
'体重' : [64, 58]
})
print(df)
print(other)
print(pd.merge(df, other, on='名前', how='inner'))
print(pd.merge(df, other, on='名前', how='outer'))
名前 役割 身長
0 田中 営業 178
1 山田 エンジニア 173
2 高橋 広報 169
3 金沢 営業 165
4 加藤 エンジニア 174
名前 体重
0 田中 64
1 金沢 58
名前 役割 身長 体重
0 田中 営業 178 64
1 金沢 営業 165 58
名前 役割 身長 体重
0 田中 営業 178 64.0
1 山田 エンジニア 173 NaN
2 高橋 広報 169 NaN
3 金沢 営業 165 58.0
4 加藤 エンジニア 174 NaN
これはSQLで言うとまんまINNE JOINとLEFT JOINですね。
ONで結合に使うキーを指定してhowで結合方法を指定する構文。
演習問題
- company.csvファイルを読み込み、階層インデックスとして読み込んでください。
なお、その際のインデックスは{"company","year","president"}であるとします。- cosme1という会社の2015年の売り上げを表示してください。
- データフレームから得られる統計量を計算し、csvファイルで出力してください。
- presidentによって、グループ分けをしたのち、presidentごとのproduct売り上げ量の平均値を取得してください。
#1
import pandas as pd
import numpy as np
df1=pd.read_csv('/content/company.csv',index_col=["company","year","president"])
print(df1)
#2
print(df1.loc[('cosme1',2015)])
#3
a=df1.describe()
a.to_csv('describe.csv')
#4
df1_grouped=df1.groupby('president')
df1_grouped.apply(np.mean)
visitors pruductA productB
company year president
cosme1 2014 James 128 15020 39523
2015 Tom 102 16102 44127
2016 Tom 101 15983 48016
2017 James 116 17240 50974
cosme2 2014 Sherry 90 13205 38309
2015 James 136 14276 42160
2016 Sherry 101 13198 36756
2017 Bob 120 16530 38455
visitors pruductA productB
president
Tom 102 16102 44127
visitors pruductA productB
president
Bob 120.000000 16530.0 38455.0
James 126.666667 15512.0 44219.0
Sherry 95.500000 13201.5 37532.5
Tom 101.500000 16042.5 46071.5
無名関数(ラムダ式)15m
無名関数
無名関数とは、名前の通り名前のない関数のことです。匿名関数とも読んだりします。
無名関数の利用シーンは、関数の名前をつける必要がない一行で終わるような関数を作る場合に利用します。
無名関数を作るには、lambdaという書式を用います。(ラムダ式)
sample = lambda i: i * 2
print(sample(10))
print(sample(100))
20
200
変数=lambda 引数1,引数2… : 式
で実装可能。
無名関数を使うメリットは引数に関数を渡すような関数を作れること、コードがシンプルになることなどがあり
l = []
for x in range(10):
l.append(x**2)
print(l)
l = list(map(lambda x: x**2, range(10)))
print(l)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
こんな形で短縮できる。
というかここにきてやっと普通の関数のmapとpandasで使うmapの違いが分かってきました。
リストとシリーズは別物ってことですね…
実際の業務で無名関数はどういったシーンで使われるのか
それでは今回学んだ無名関数が、実際の業務でどういったシーンで使われるのか説明します。
データ分析業務の場合ですと、主にデータの前処理で用いられます。
import pandas as pd
dic={'Group_A':[166,172,173,174,165],'Group_B':[172,181,172,171,166]}
df=pd.DataFrame(dic)
print(df)
df['diff']=df.Group_A-df.Group_B
print(df)
Group_A Group_B
0 166 172
1 172 181
2 173 172
3 174 171
4 165 166
Group_A Group_B diff
0 166 172 -6
1 172 181 -9
2 173 172 1
3 174 171 3
4 165 166 -1
これじゃダメなんだろうか…
8日目の感想
Pandas応用がかなりボリュームあったので時間がかかってしまいましたが一通り抑えることはできたかなと思います。
mapやfilterなんかの(これ何のために使うんや…)ってものから
無名関数やmerge、describeなどこれは便利そうだな!ってものまでインプットがかなり多かったので
ちゃんと見直して理解を深めておきたいと思います。
思ってた以上に時間はかかってますが気づけば明日でインプットは最終日です。
多分来週からKaggleのデータを触ってみるフェイズに入ると思うので今週中に今までの見直しをしておこうと思います。
明日も頑張れ