見出し画像

lambda式で回答がきました

会社で使うリストでどうしても解決できなかったことがようやく解決しました。
やりたかったことは、受注リストの商品コードに特定の文字列が存在する場合の受注個数の日ごとの集計。
pythonのライブラリのpandasを主に使っています。

teratailを利用しました。
teratailとはプログラミングに関する質問をすると親切な方が回答してくれるというありがたいサイト。

質問内容はこんな感じ。

日付,種類,数量
2月1日,サクラA,3
2月1日,サクラB,20
2月1日,サクラA,5
2月1日,ウメA,3
2月2日,ウメB,2
2月2日,モモA,1
2月3日,サクラC,2
2月4日,ウメA,3
2月5日,サクラA,10
2月5日,サクラA,12

上記のようなデータがあるとします。
種類に 「サクラ」 を含む数量の合計を0も含め日付順にリストで取得したいです。
この表の場合なら
[28, 0, 2, 0, 22]
このリストが欲しいです。
groupbyを使うと思われるのですが、
「サクラ」を含むという条件をつけると
取得したい結果が得られなくて困っています。


こんな質問をしたところものの30分もしない間に回答がきました。

import pandas as pd
import io

csv_data = '''
日付,種類,数量
2月1日,サクラA,3
2月1日,サクラB,20
2月1日,サクラA,5
2月1日,ウメA,3
2月2日,ウメB,2
2月2日,モモA,1
2月3日,サクラC,2
2月4日,ウメA,3
2月5日,サクラA,10
2月5日,サクラA,12
'''
df = pd.read_csv(io.StringIO(csv_data))

#
count = (
  df.groupby('日付')
    .apply(
      lambda x: x.loc[x['種類'].str.contains(r'^サクラ'), '数量'].sum())
    .to_list())

print(count)

#
[28, 0, 2, 0, 22]

lambda式、存在は知ってはいましたが使い方がよくわからなくて避けてきました。でも普通にこれを使って回答が来ました。
apply、この関数は初めて知りましたし、調べている最中もこの関数に出くわすことはありませんでした。どうりで解決できないわけです。
というわけで、お勉強がてらこのコードを解析をしていこうと思います。

まずはgroupby
列ごとに集計してくれる便利な関数。
質問に使ったデータフレームを使うとこんな感じで使えます。

df.groupby('日付').size()

#日付
#2月1日    4
#2月2日    2
#2月3日    1
#2月4日    1
#2月5日    2

日付でグルーピングするとこんな感じ。ちなみにsizeは要素数を取得する関数。
種類でグルーピングするとこう。

df.groupby('種類').size()

#種類
#ウメA     2
#ウメB     1
#サクラA    4
#サクラB    1
#サクラC    1
#モモA     1

とても簡単に使えてとても便利な優れものの関数です。
これは知っていました。

で、問題は次。
初めて知るapplyと避けてきたlambda式。
applyはlambdaとセットで使うことがよくあるそうです。

lambda式は無名関数とも呼ばれていて、文字通り名前のない関数のようなもので、使いまわすようなことがないようなちょっとした関数の代わりに使うことが多いようです。

例えばa+bを普通の関数で表すとこうなります。

def calc(a, b):
    return a + b
calc(1, 2)
#3


これをlambda式で表すとこうなります。

lambda a,  b: a+ b

便宜上これをfとすると

f = lambda a, b: a + b
f(1,2)
#3

引数を入れてやれば同じ結果が得られます。
ちなみにf = lambda ~ とするのは無名関数なのに名前をつけるのはおかしいということで今は非推奨らしいです。名前をつけるならdefを使いなさいということです。ただし今やったようにf = lambda ~ としても問題なく動きます。

で、今回いただいた回答によると

count = (
  df.groupby('日付')
    .apply(
      lambda x: x.loc[x['種類'].str.contains(r'^サクラ'), '数量'].sum())
    .to_list())

いろいろ組み合わさって複雑になっています。
そもそもこの場合のlambdaの引数は何になるのか?
恐らくdf.groupby('日付')になるのだと思います。
ググってはみたもののこういう場合の引数が何になるか見つけられませんでしたが、df.groupby('日付')以外に考えられませんのでそういうことにしておきます。

locはDataFrameの任意の位置を取得するためのプロパティ。
関数ではないのでdf.loc()ではなく、df.loc[ ]のように使うらしい。
第1引数で行、第2引数で列を指定。

applyはデータフレーム、seriesの行全体や列全体に対して、同じ操作をしたいときに使用するとのこと。引数に関数を使うことで関数を適用させることができます。
よくわからない点もありますが、要はデータフレームなどを関数を適用させて操作できるということだと思います。


ということで、今回いただいた回答を言語化すると、

変数【count】はデータフレームを日付でグルーピングしたもの【df.groupby('日付')】の、種類の列の先頭に文字列サクラを含むものの数量【 lambda x: x.loc[x['種類'].str.contains(r'^サクラ'), '数量']】を合計【.sum()】し、リストにしたもの【.to_list()】ということになります。

というわけで
2月1日は 3 + 25 + 5 = 28
2月2日は サクラがないので 0
2月3日は 2
2月4日も サクラがないので 0
2月5日は 10 + 12 = 22
となり
[28, 0, 2, 0, 22] というリストが得られることになります。

ちなみに「^」はハットまたはカレットと読むらしいのですが、文字列の先頭を示す正規表現です。先頭にこだわらずサクラを含むだけでよいなら.str.contains('サクラ')でも問題ありません。

多少解釈が間違っているところもあるかもしれませんが大体こんな感じと理解しておきます。

結構悩んで諦めかけていたことなので大変助かりました。
こんなことならもっと早く質問しておけばよかった。


この記事が気に入ったらサポートをしてみませんか?