Python学習記録_8日目.関数(中級)・pandas応用・無名関数

# 元記事
[Python学習記録_プログラミングガチ初心者がKaggle参加を目指す日記](https://qiita.com/noway6064/items/14ab494dc65d3040f733)
8日目です。
今日はPandasの応用ということでここでも実用的なものがありそうで楽しみですね。
…というか気づけば残すところ今日と明日で最低限必要な知識が全て身につく(はず)みたいですが本当にこのレベルでKaggle参加できるのかちょっと不安ではあります。
まあ結局のところ実際にやってみないと分からないことだらけだと思うのでとりあえずはデータに触ってみる、というのができるようになることを目標に今日も頑張っていきましょう。

### 関数 中級(zip,map,filter) 25m 

##### 名前付き引数の指定
>Pythonは引数を指定する時に、引数の名前を指定して関数を呼び出すことが出来ます。
このことを、名前付き引数やキーワード引数と呼んだりします。
```python
def sample(weight, height):
    print("体重は"+str(weight) + "kg")
    print("身長は"+str(height) + "cm")

sample(50, 170) 
sample(170,50)  
```
```
体重は50kg
身長は170cm
体重は170kg
身長は50cm
```
こんな形で本来第1引数には体重を渡さないといけないのに身長を渡してしまう、みたいなミスが起きるけど
名前付きで引数を指定してあげることでそういうミスを防ぐことができるとのこと。

```python
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つでも可)のリストやタプルを、要素がタプルのリストにまとめることが出来る組み込み関数です。
```python
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()を用いる事で可能です。
```python
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つも可能で
```python
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引数にシーケンスと呼ばれる、複数ある値をひとかたまりとして格納した値を渡します。

```python
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文でも代替できそうな気がしますね。
```python
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関数は、ある値より大きい値だけを取り出したい場合や、特定の文字列を含む要素を取り出したい場合に利用します。
```python
def is_mod(x):
    return x % 3 == 1

li = list(filter(is_mod, range(1 ,10)))
print(li)
```
```
[1, 4, 7]
```
これもforとifを使えば代用できそうな…
```python
li = []
for i in range(1,10):
  if i % 3==1:
    li.append(i)
print(li)    
```
```
[1, 4, 7]
```
mapと同じ気配を感じます。

##### 再帰関数
>再帰関数とは、自分自身を呼び出す関数のことです。
```python
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](https://sinhrks.hatenablog.com/entry/2015/06/18/221747)
>pandas ではデータを 列 や 表形式のデータ構造として扱うが、これらのデータから順番に値を取得 (イテレーション) して何か操作をしたい / また 何らかの関数を適用したい、ということがよくある。

複数のデータに対して順番に値を取得すること=イテレーションという認識で良いのかな…？

そしてGROUP BYがでてきました。
SQL初めてやった時はグループ化の概念が全然理解できなくてハゲあがりましたが
pythonでも考え方は同じ感じなんでしょうか
```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とはまた別物なのか…？
リストに使えるってことはシリーズにも使えるような気がするんですがどうでしょう。

```python
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()を使用します。
```python
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などで、複数の列をインデックスとして持たせる機能です。
なので、データを一意に決定するには複数のインデックスを指定する必要があります。
まず、階層型インデックスが必要なデータの特徴は、次の通りです。
・ユニークな列がなく、どれが実測値か分からないデータ
・いくつかのインデックスを指定して初めて実測値が一意に定まる

```python
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の売上が欲しい時には
```python
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])
```
こんな形で店舗名・年度・商品名のすべてを指定しないといけないのでスクリプトが長くなってしまう
これを階層型インデックスとして指定してあげると
```python
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) を指定できます。

```python
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%）などがあります。
```python
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’）における相関行列は以下のように求めることが出来ます。
相関係数の絶対値が高ければ高いだけ、データの間に相関があるといえます。
```python
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を用いることでデータを結合する事が可能です。
```python
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で結合方法を指定する構文。

##### 演習問題
>1. company.csvファイルを読み込み、階層インデックスとして読み込んでください。
なお、その際のインデックスは｛"company","year","president"｝であるとします。
>2. cosme1という会社の2015年の売り上げを表示してください。
>3. データフレームから得られる統計量を計算し、csvファイルで出力してください。
>4. presidentによって、グループ分けをしたのち、presidentごとのproduct売り上げ量の平均値を取得してください。

```python
 #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という書式を用います。（ラムダ式）

```python
sample = lambda i: i * 2
print(sample(10))
print(sample(100))
```
```
20
200
```
変数=lambda 引数1,引数2… : 式
で実装可能。
無名関数を使うメリットは引数に関数を渡すような関数を作れること、コードがシンプルになることなどがあり
```python
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の違いが分かってきました。
リストとシリーズは別物ってことですね…

##### 実際の業務で無名関数はどういったシーンで使われるのか
>それでは今回学んだ無名関数が、実際の業務でどういったシーンで使われるのか説明します。
データ分析業務の場合ですと、主にデータの前処理で用いられます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252820/22063726-ac3e-f692-658e-4a1ea3b3021b.png)

```python
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のデータを触ってみるフェイズに入ると思うので今週中に今までの見直しをしておこうと思います。
明日も頑張れ