Pandas入門講座|14.SeriesやDataFrameに関数を適用する方法(map, apply, applymapメソッド)【PythonのライブラリPandas】

はじめに

この動画では、pandasのmap, applymap, applyについて学びます。 これまでのレッスンでは、作成したデータフレームを並び替えたり、集約や集計、結合の方法を学んできました。 これだけでもデータ解析の前処理、簡単な分析あればできるでしょう。 ですが、もっと細かくデータ前処理をしたい場合、データフレーム全体に関数を適用させたり、文字列操作をしたい場面が出てくると思います。 そういったときに役立つのがmapやapplymap,applyです。 それでは、いってみましょう!

数値Seriesとmapメソッド

import pandas as pd
import numpy as np

はじめに、PandasとNumPyをインポートします。
NumPyは、Pythonで高速に数値の計算をするために様々な数学関数を提供するライブラリです。

s1 = pd.Series([7000, 5000, 23000, 2500, 12000])
s1

それでは、シリーズを作成してmapの使い方をみていきましょう。
変数s1に、5つの要素のシリーズを代入します。実行します。
このようなデータです。商品単価をイメージして作成しています。
まずはこのシリーズの値を、全て2倍にしてみましょう。

s1 * 2

レッスン5のSeriesでは、このように記述しました。
2倍にするだけであれば、こうやって計算することは簡単です。

def double(x):
return x * 2

では、mapメソッドを使って、シリーズを2倍にしてみましょう。
mapメソッドは、シリーズ全ての要素に同じ関数を適用したいときに使います。
シリーズを2倍にする関数を作って、適用させてみましょう。
まずは、「数値を2倍にする」という関数を作ります。
関数の作り方は、Python超入門コースのレッスン12で説明しています。詳しく知りたい方は、今右上に出ているカードをクリックしてください。
関数を作るには、def 関数名です。関数名をdoubleとします。ここで引数 i を渡すと、かける2になる関数を作ります。

s1.map(double)

作った関数を、mapメソッドで適用させてみましょう。
シリーズ ドットmap、丸括弧には適用させたい関数を渡します。作成したdoubleを記述しましょう。
実行します。
全ての値が2倍になりました。

s1.map(lambda x: x * 2)

このようなシンプルな関数の場合や、何度も呼び出して使用しない関数は、defで宣言せずに関数を作ることができます。
名前のない関数で無名関数と言い、ラムダ式という方法です。
mapやあとで説明するapply,applymapとセットで使う場面が多いので簡単に使い方を説明します。
詳しい説明はPython入門で説明しますので、少々お待ちください。
では、先ほどと同様に、シリーズ全体を2倍にしてみましょう。
sドットmapで、カッコの中は関数でしたね。ここにラムダ式の関数を書きます。
lambda半角スペース、そして xです。コロンに続けて、計算したい内容を書きます。2倍にするので x* 2です。
ここでxを使用しましたが、xでもyでも、ここでしか使わない変数なのでなんでもよいです。
コロンの左が引数、右を返り値と言います。
なので、xにシリーズそれぞれの要素が代入され、x * 2を計算した結果が返されます。
実行します。シリーズ全体の要素に2倍になる式を適用できました。
ラムダ式は、さっと計算したいときに便利です。

s1.map(lambda x: ‘1万円以上’ if x >= 10000 else ‘1万円未満’)

今度はラムダ式で、if文を試してみましょう。
s1.mapで適用させたい関数として、ラムダ式で条件を書きます。
10000より小さければ「1万円以上」、それ以外は「1万円未満」と表示する条件です。
実行してみましょう。if条件も適用することができました。

def f_rank(x):
if x >= 20000:
return ‘S’
elif x >= 10000:
return ‘A’
elif x >= 5000:
return ‘B’
else:
return ‘C’

if文の関数も適用させてみましょう。
商品の価格に応じて、価格帯のランクをつけるという関数です。
2万以上をSとして、1万以上はA、5000以上はB、それ以外はCとします。

s1.map(f_rank)

では、この関数をシリーズに適用させてみましょう。
実行します。それぞれの数値に応じて条件通りにランクが表示できました。

s1.map(np.square)

次に、numpy関数を使って、シリーズを二乗してみましょう。二乗する関数は、np.squareです。
実行します。numpy関数を適用する計算もできました。

s1.map(np.sum)

次は、numpy関数で合計を計算するsum関数を適用させてみましょう。
実行します。結果が表示されましたが、シリーズそのままの数値です。
これは、mapメソッドは、シリーズの各要素に対して処理をするからです。例えば、np.sim(7000)と計算しているので、このような結果になります。

s1.sum()

シリーズの集計をしたい場合は、シリーズのsumメソッドで計算できます。同様に、平均などはじめから用意されているメソッドは、そちらを使うのがよいでしょう。

s2 = pd.Series([‘スカートSkirt’, ‘ニットKnit’, ‘ジャケットJacket’, ‘シャツShirt’, ‘ロングパンツSlacks’])
s2

それでは次に、文字列のシリーズでもmap関数を使ってみましょう。
それでは、文字列のシリーズを作成します。漢字とアルファベットが混ざった文字列を作ります。このようなシリーズです。

s2.map(lambda x: x[0])

全ての要素のはじめの一文字だけ取り出してみましょう。
これもラムダ式で書くことができます。
文字列操作については、Python入門で詳しく説明しますが、要素のはじめの文字を取り出すには、リストで要素の番号を指定します。
実行します。はじめの一文字を取り出せました。

import re
s2.map(lambda x: re.findall(‘[A-z]+’, x))

次は、ここから、アルファベットだけを抜き出してみましょう。
文字列操作に便利な正規表現のモジュールreをインポートします。
この正規表現についてもPython入門で詳しく説明しますので、こんなことも簡単にできるということを知っていただければと思います。
s2ドットmap、関数を記述します。ラムダ式で、文字列からアルファベットの単語だけを探す、という関数を書きます。
実行します。アルファベット部分だけを抜き出すことができました。
ここで、結果がリストで格納されました。なぜでしょう?これは、re.findallの戻り値はリストで返ってくる設定だからです。

s3 = pd.Series([‘スカートSkirt’, ‘ニットKnit’, ‘ジャケットJacket’, ‘シャツShirt’, ‘BottomsロングパンツSlacks’])
s3

先ほど作ったシリーズの一番最後の要素を少し変えて、シリーズを作成します。

s3.map(lambda x: re.findall(‘[A-z]+’, x))

このシリーズから、アルファベット部分を抜き出してみましょう。

このように、[Bottoms, Slacks]と複数ある場合もリストであれば1つの要素に格納できます。

s4 = pd.Series([‘スカートSkirt’, ‘ニットKnit’, ‘ジャケットJacket’, ‘シャツShirt’, np.nan])
s4

それでは次に、先ほどの文字列のシリーズに、欠損値のデータを追加してみます。1つの要素をnp.nanで欠損値にしました。このようなシリーズです。

d = {‘ジャケットJacket’: ‘Outer’, ‘スカートSkirt’: ‘Bottoms’, ‘ニットKnit’: ‘Tops’,’ワンピースOnepiece’:’Onepiece’ }
d

そして、これらに対応するような辞書型のデータを作成します。辞書には、シャツに対応するデータがなく、シリーズにないワンピースがあります。
辞書型とは配列の一種で、二つの要素をセットにして格納できるデータ型です。セットにしている前の方をKeyといい、後の方をValueと言います。こちらも詳しくはPython入門で説明します。

s4.map(d)

文字列のシリーズに、この辞書型のdを適用します。2つのデータを関連づけるという操作です。これをプログラミング用語ではマッピングと言います。
Excelのルックアップをするようなイメージです。シリーズの要素と辞書のKeyを関連づけて、そのvalueを返します。
では実行します。それぞれKeyに対応したValue部分が返ってきました。
辞書のデータになかったシャツと欠損値は、NaNとなりました。

s4.map(‘{}を買います。’.format)

また、mapメソッドを使って、シリーズの各要素から、文章を作成することができます。ここで使うformatも文字列操作のひとつです。こちらも詳しくはPython入門で説明します。
formatを使うと、波かっこの中にシリーズの要素が入り、文章を作成することができます。
実行してみましょう。シリーズの要素からそれぞれ文章が作成されました。しかし、欠損値までそのまま作成されました。

s4.map(‘{}を買います。’.format, na_action=’ignore’)

これを、欠損値の場合はそのまま処理をしない、としたい場合は、引数na_actionに’ignore’を渡します。
実行します。欠損値はそのままNaNとなりました。

数値Seriesとapplyメソッド

s1

さて次は、シリーズとapplyメソッドをみていきましょう。もう一度数値のシリーズのs1を表示します。

s1.apply(double)

applyメソッドも、mapメソッドと同じように、シリーズ全ての要素にそれぞれ関数などの同じ処理を適用させたいときに使います。
使い方はもmapメソッドと同じように、丸括弧の中に適用させたい関数を記述します。
先ほど作成した要素を2倍にする関数、doubleを適用させてみましょう。実行します。
問題なく計算できました。

s1.apply(lambda x: x*2)

要素を2倍にする関数を、ラムダ式でも書いてみましょう。実行します。
こちらも計算できました。

s1.apply(lambda x: ‘1万円以上’ if x >= 10000 else ‘1万円未満’)

また、ラムダ式で、if文を使った条件を適用させてみましょう。
実行します。こちらも先ほどと同じ結果で表示できました。

s1.apply(func_flag)

続けて、先ほど作成した、if文でいくつか条件分岐する関数も適用してみましょう。
実行します。これも先ほどと同じ結果になりました。

def f_tax(x, tax):
return x * tax + x

ここでもう一つ、消費税率を渡すと税込の金額を計算する関数を作ります。
引数名はtaxとしました。

s1.apply(f_tax, args=(0.1,))

この関数を適用する場合、applyメソッドの引数argsに税率をタプルで渡します。タプルにするのは、引数が複数の場合もこうやって渡すことができるからです。
タプルとは、複数の要素が順序を持って並べられているデータ型のことです。
リストとも似ていますが、タプルは要素の変更や削除ができません。
実行します。計算できました。

s1.map(f_tax, args=(0.1,))

さてこの関数をmapメソッドで使うことができるのでしょうか。
エラーになりました。mapメソッドには引数を渡す手段がないので、このような関数には適していません。

s1.apply(np.square)

次は、numpy関数を使ってみましょう。二乗する関数です。
実行します。こちらも計算できましたね。

s1.apply(np.sum)

mapメソッドで計算できなかった、合計はどうでしょうか。
実行します。計算できませんでした。こちらもmap同様の結果です。それぞれの要素だけを合計しているので、np.sum(7000)が計算されています。

文字列Seriesとapplyメソッド

s2

では、文字列のシリーズでapplyメソッドをみていきましょう。
文字列のシリーズは、このs2で試してみましょう。

s2.apply(lambda x: x[0])

このシリーズの各要素の最初の一文字を取り出します。
これもできましたね。

s2.apply(lambda x: re.findall(‘[A-z]+’, x))

続いて、アルファベットだけ取り出してみましょう。
先ほどと同じように取り出せました。

s2.apply(d)

では、関連づけをするマッピングです。実行します。
これはエラーになりました。applyメソッドではこのような操作はできません。
mapメソッドとapplyメソッドの共通点は、全ての要素にそれぞれ同じ処理をすることです。
一方、mapメソッドしかできないことは、何かの情報と関連づけるという操作です。
これは、mapメソッドとapplyメソッドの大きな違いのひとつと言えます。

s3.apply(‘{}を買います。’.format)

次に、formatを使って文章を作成してみましょう。
こちらも同じ結果が返ってきました。

s3.apply(‘{}を買います。’.format, na_action=’ignore’)

ただし、applayメソッドにはmapメソッドにあるような欠損値の処理の仕方を指定する方法がありません。用途に応じて使い分けるとよいでしょう。

DataFrameとapplymapメソッド

df = pd.DataFrame(
[[11000, 6000, 8000],[5000, 12000, 6000],[4000, 5000, 9000]], columns=[‘1日’, ‘2日’, ‘3日’], index=[‘Aさん’, ‘Bさん’, ‘Cさん’] )
df

次はデータフレームです。データフレームで使えるのは、applymapメソッドと、applyメソッドです。
まず、データフレームを作成しましょう。このように、カラム名は日付で、インデックス名は名前とします。3行3列のデータフレームです。

df * 2

それではデータフレームを2倍にしてみましょう。
これだけでしたら、関数を使わなくてもこのように計算することができます。

df.applymap(double)

しかし、2倍にする関数を作ったので、この関数を適用してみましょう。
データフレーム全体に関数を適用したい場合は、applymapを使用します。
これも使い方は同じで、dfドットapplymapに関数を渡します。作成した2倍にする関数、doubleを適用させてみましょう。
実行します。
データフレーム全ての要素が2倍になりました。

df = df.applymap(lambda x : x * 2)

ラムダ式でも試してみましょう。実行します。
こちらも2倍になりました。

df.applymap(lambda x ‘1万円以上’ if x >= 10000 else ‘1万円未満’)

次に、ラムダ式にif文を使ってみます。要素の数値に応じて「1万円以上」と「1万円未満」と表示してみましょう。
条件の書き方は先ほどのシリーズのときと同じです。1万以上とそれ以外という条件です。
実行します。うまく表示できました。
データにフラグを付けたいような場面で役立ちます。

df.applymap(f_rank)

次は、if文で価格のランクをつける関数f_rankを適用させてみましょう。実行します。
条件に応じた文字列が表示されました。こちらもフラグ付に役立ちます。

df.applymap(f_tax, args=(0.1,))

税込金額を計算する関数は適用できるのでしょうか。
実行します。
エラーになりました。シリーズのmapメソッド同様に、引数を渡す手段がありません。

DataFrameとmask,where

df.mask(df < 10000, ‘1万円未満’)

さて、簡単な条件でフラグを付けたい場合、maskメソッドやwhereメソッドを使う方法もあります。
maskメソッドは、条件に合う場合に指定した値を返すことができます。
このように、条件に合う要素だけが「1万円未満」になりました。

df < 10000

条件部分だけ書き出したコードも実行して比べてみましょう。

df.where(df < 10000, ‘1万円以上’)

そして、whereメソッドは、maskメソッドとは反対で、条件に合わない要素を指定した値で返すことができます。
実行します。ここでFalseとなる要素のみ「1万円以上」が表示されました。

DataFrameとapplymapメソッド

df.applymap(np.square)

さて、applymapメソッドに話を戻します。numpy関数を使ってデータフレームを二乗してみましょう。
実行します。こちらも関数を適用して計算することができました。

df.applymap(np.sum)

合計はどうでしょうか。
これは計算できませんでした。applymapメソッドは、データフレームのそれぞれの要素に関数を適用することができます。つまり、np.sum(11000)を計算していることになるため、データフレームの合計を出すことはできません。

df.sum()

文字列DataFrameとapplymapメソッド

データフレームの合計を算出したい場合は、シリーズと同様、データフレームのsumメソッドで計算できます。

df2 = pd.DataFrame([[‘スカートSkirt’, ‘ニットKnit’, ‘ジャケットJacket’],
[‘シャツShirt’, ‘ロングパンツSlacks’, ‘ワンピースOnepiesce’]],
columns=[‘x’, ‘y’, ‘z’])
df2

次に、文字列のデータフレームを作って、applymapメソッドの使い方をみてみましょう。このようなデータです。

df2.applymap(lambda x: x[0])

最初の文字を取り出してみましょう。関数のコードはシリーズの時と同じです。
実行します。それぞれの要素の最初の文字だけのデータフレームが返ってきました。

df2.applymap(lambda x: re.findall(‘[A-z]+’, x))

次に、このデータフレームからアルファベット部分だけを取り出してみましょう。
こちらも、シリーズで試した関数と同じです。
実行します。
アルファベット部分だけが取り出されたデータフレームが返ってきました。
データフレームのそれぞれの要素に何かをしたい場合は、applymapを使うとよいでしょう。

DataFrameとapplyメソッド

df.apply(double)

最後に、データフレームとapplyメソッドを見てみましょう。
最初に作った、要素を2倍にする関数doubleを適用させてみます。
applyメソッドでも問題なく計算できました。

df.apply(lambda x : x * 2)

では、ラムダ式で2倍にしてみましょう。
こちらも計算できました。

df.apply(lambda x: ‘1万円以上’ if x >= 10000 else ‘1万円未満’)

次に、ラムダ式で条件分岐させてみましょう。関数は今までと同様、1万以上かどうかの条件です。
実行します。エラーになりました。これは、各要素に対する処理なのか、まとまりのデータに対する処理なのか判断できないとエラーです。
プログラミングでは条件を与えるとTrueかFalseのどちらかが返ってきます。ただし、これがデータのまとまりになっている場合に、それぞれの要素の値はTrueかFalseでも、まとまり全体としては値が定まらないということです。これはPandasの仕様でこうなっていますので、データフレームとapplyメソッドの組み合わせでは、if文は適用できないと覚えてしまいましょう。

df.apply(lambda x: x >= 10000)

ただし、if文ではなく、ただ判定するだけれあればエラーになりません。これは、全ての要素に対してそれぞれを判定して結果を返すことができるからです。

df[‘1日’].apply(lambda x: ‘1万円以上’ if x >= 10000 else ‘1万円未満’)

先ほどのような条件式を含む関数をapplyメソッドに適用させたい場合は、データフレームからカラムを指定して適用させます。
レッスン5のシリーズで説明しましたが、データフレームから1列取り出すとシリーズになります。
そしてシリーズはapplyメソッドを使うことができました。実行してみましょう。
このように、データフレームもカラムを指定すれば、applyメソッドで使うことができます。

df.apply(f_tax, args=(0.1,))

次は、消費税込み金額を計算する関数を試してみましょう。
実行します。計算することができました。

df.apply(np.square)

さて、次はデータフレームにnumpy関数を適用させてみましょう。
二乗にする関数squareで二乗にします。
実行します。これは計算できました。

df.apply(np.sum)

次に、データフレームに、numpyのsum関数を渡してみます。
合計を計算する関数です。実行します。
インデックスがABCとなり、列ごとの合計が計算されました。
二乗する関数と合計する関数の違いは、集計するかどうかにあります。このように合計のような集計関数を使うと、列方向、行方向で計算することができます。
デフォルトでは、列方向の計算が適用されます。

df.apply(np.sum, axis=1)

行の方向に計算をしたい場合は、引数axisに1を渡します。実行します。
インデックスがAさんBさんCさんとなりました。行方向に計算できています。
このように、データフレームでapplyメソッドで集計関数などを使用する際は、引数axisで計算方向を指定できます。

df.apply(lambda i: max(i) – min(i))

列の中で、最大値から最小値を引くこともできます。売上の高い人と低い人の差を計算する、といったケースです。
これもラムダ式で計算できます。引数axisを指定しない場合は、列で取り出して計算し、結果を返します。
実行します。
1日のカラムは、最大値11000引く最小値4000で7000ですね、列ごとに計算できています。

文字列DataFrameとapplyメソッド

df2.apply(lambda x: x[0])

文字列のデータフレームでapplyメソッドは使えるのでしょうか。
最初の一文字を取り出してみましょう。実行します。
最初の一文字ではなく、最初の行が取得できました。
これはつまり、axisで指定していないので列ごとの1番目を取得していることになります。x列の1番目、y列の1番目、z列の1番目ということです。

df2.apply(lambda x: re.findall(‘[A-z]+’,x))

では、applyメソッドで、文字列のデータフレームからアルファベット部分だけを取り出せるのでしょうか。
実行します。
これはエラーになりました。先ほど同様、列で処理をしようとしているのでエラーになってしまいます。

さて、ちょっと混乱してきましたね。最後にまとめてみましょう。
シリーズで使えるメソッドは、mapとapplyの2つです。
いずれもそれぞれの要素に対して、関数を適用させたりすることができます。
では、どういった使い分けをするのがよいのでしょう?
ラムダ式を使って2倍にすること、if文つきのラムダ式、二乗するといったnumpy の数学関数を使うことはどちらもできました。
文字列操作は、mapメソッドが向いています。また、関連付けをするマッピングができるのはmapメソッドです。
引数があるような関数を適用させたい場合はapplyメソッドが向いています。
ですので、私の場合ですが、文字列を扱うときはmapメソッド、数字を扱うときはapplyメソッドを使う場面が多いかなと感じています。
次に、データフレームで使えるメソッドは、applymapとapplyでした。
applymapメソッドは、それぞれの要素に対して関数を適用させます。
一方のapplyメソッドは、適用させたい関数の種類によって、それぞれの要素に対してか、列や行ごとへの適用ができます。
ラムダ式で2倍にしたり、numpy数学関数を使う場合はどちらでもできました。
if文付きの関数や文字列操作に適しているのはapplymapメソッドです。
一方、集計をしたい場合や引数がある関数を適用させたい場合はapplyメソッドを使うのがよいでしょう。
最後にもう一度おさらいです。
シリーズで使うmapメソッドとapplyメソッドの大きな違いは、mapメソッドはマッピングができて、文字列操作が得意ということです。
一方、applyメソッドは引数のある関数を使えることです。
データフレームで使うappllymapメソッドとapplyメソッドの違いは、applymapメソッドがそれぞれの要素にしか関数を適用することができない点です。
一方、applyメソッドは行ごとや列ごとに適用することができます。
以上は、私の理解ではありますが、参考になさってみてください。

以上がmap, apply,applymapの解説です。
解説の通り、かなり細かいデータの前処理が可能ですよね。
前処理、集計や分析など怖いものなしのはずです!
キノコードではPandas入門コースの他に、仕事に活かせるPythonの自動化のレッスン、株のデータ分析のレッスンをアップしています。
また、人工知能のレッスン、データ可視化のレッスン、Pythonを使ったWebアプリ開発の動画もアップしていく予定です。
動画をあっぷしたときに新着通知が届くので、ぜひチャンネル登録をお願いします。

レッスンで使ったファイルはこちら

■保存方法
Mac:右クリック⇒「リンク先を別名で保存」
Windows:右クリック⇒「名前を付けてリンク先を保存」

Jupyter Labのファイルはこちら