質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.33%

グループ毎の特定の列の要素数を条件付きで数えたい & グループ毎の特定の列の要素を文字列連結して新規列に代入したい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 452

jinyo

score 34

はじめに

お世話になります。
Pandasで加工した下記のデータフレーム(以下DF)につきまして、求める要件2点があります。
実現方法についてお知恵をお貸しいただきたいです。

品番 品名 部品 単価 生産数
111 製品AAA A 0.5 1,000
111 製品AAA B2 0.5 1,000
111 製品AAA C 0.5 1,000
111 製品AAA ふくろ 0.5 1,000
111 製品AAA 0.5 1,000
222 製品BBB C 0.5 2,000
222 製品BBB 0.5 2,000
333 製品CCC A1 0.5 3,000
333 製品CCC A2 0.5 3,000
333 製品CCC 0.5 3,000

やりたいこと

質問のタイトルが適切でないような気がして申し訳ないですが、やりたいことは下記の二つです。

要件1

上記のDFに新規列[部品項目数]という列を作り、商品毎の部品列の要素数のcountを出したいです。
これだけなら自分で解決できるのですが、下記の条件があります。

条件:部品列の"ふくろ"、"箱"という値に関してはカウントしない

商品毎に"ふくろ"だけがある商品、"箱"だけがある商品、両方ある、両方ない、パターンがあります。

要件2

商品毎の部品列の要素を文字列連結したいです。つまり商品Aであれば部品は["A","B2","C","ふくろ","箱"]がありますので、要件Aとおなじく"ふくろ","箱"は無視したそれ以外の値を、「"A,B2,C"」といったような形でひとつの文字列にし、そしてそれを新規列["部品一覧"]列に挿入したいです。

求める結果

つまり、下記のようなDFを取得したい、というのが目的になります。

品番 品名 部品 単価 生産数 部品項目数 部品一覧
111 製品AAA A 0.5 1,000 3 A,B2,C
222 製品BBB C 0.5 2,000 1 C
333 製品CCC A1 0.5 3,000 2 A1,A2

解決のために実施したこと

品番の数だけ、行数を数えましたが、"ふくろ"、"箱"を無視する方法がわかりませんでした。

pivot_df = df.pivot_table(values="総生産数" ,index=['品番'],columns="部品",
    aggfunc="count" ,fill_value=0,dropna=False)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

要件1は、予め部品列の値が'ふくろ','箱'の行を削除したデータフレームを作成しておくと良いかと思います。
要件2に関しては、groupby.apply() または groupby.agg()などを使い Seriesをtupleやlistに変換することで実現できます。

import pandas as pd
import io
data = """
品番,品名,部品,単価,生産数
111,製品AAA,A,0.5,1000
111,製品AAA,B2,0.5,1000
111,製品AAA,C,0.5,1000
111,製品AAA,ふくろ,0.5,1000
111,製品AAA,箱,0.5,1000
222,製品BBB,C,0.5,2000
222,製品BBB,箱,0.5,2000
333,製品CCC,A1,0.5,3000
333,製品CCC,A2,0.5,3000
333,製品CCC,箱,0.5,3000
"""

df = pd.read_csv(io.StringIO(data))
tmp = df[~df['部品'].isin(['箱', 'ふくろ'])]
res = tmp.groupby(['品番','品名']).agg({'単価':'first','生産数':'first','部品':['count',lambda d:tuple(d)]})
res.columns = ['単価','生産数','部品項目数','部品一覧']
print(res)
#            単価   生産数  部品項目数        部品一覧
#品番  品名                                 
#111 製品AAA  0.5  1000      3  (A, B2, C)
#222 製品BBB  0.5  2000      1        (C,)
#333 製品CCC  0.5  3000      2    (A1, A2)

【追記】

上記のコードを 先に groupby() してから agg() の中で "箱"と"ふくろ"を処理しないように修正してみます。

def conv_parts_to_tuple(d):
    return tuple(d[~d.isin(['箱', 'ふくろ'])])

def num_of_parts(d):
    return d[~d.isin(['箱', 'ふくろ'])].count()

res = df.groupby(['品番','品名']).agg(
    {
        '単価':'first',
        '生産数':'first',
        '部品': [ num_of_parts,
                  conv_parts_to_tuple,]
    })
res.columns = ['単価','生産数','部品項目数','部品一覧']

やっていることはほとんどど同じなのですが、 agg()内で2つのlambda(無名関数)を指定することが困難そうだったので、関数として切り出して処理しております。

"箱"と"ふくろ"の処理部が2つの関数に含まれて若干冗長な感じはあるのですが、この程度であればまあ個人的には問題ないかと思います。

ただ複数個の関数を切り出す必用があるのであれあ、apply()を使って1つの関数で処理したほうがシンプルになる気もしますね・・。

やってみます。

def fnc(row):
    tmp = row.loc[~row['部品'].isin(['箱', 'ふくろ']), '部品']
    return pd.Series(
        {
            '単価': row['単価'].iat[0],
            '生産数': row['生産数'].iat[0],
            '部品項目数': tmp.count(),
            '部品一覧': tuple(tmp)
        })

res = df.groupby(['品番','品名']).apply(fnc)
print(res)

apply()の場合は Series型のデータを返すことで列を構成します。

この方法であれば

  • "箱"と"ふくろ"の処理部を共通化できる
  • Column名を直に指定できるため変更の必要がない

のですこしシンプルになります。まあ、好みの問題ではありますが。。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/04/01 00:45

    magichan様
    お世話になります。素晴らしいです。最高です。
    groupby.apply()やgroupby.agg()は知りませんでしたので早速明日トライしてみます!

    そして大変申し訳ないのですが、お伝え忘れたこととして、
    「予め部品列の値が'ふくろ','箱'の行を削除したデータフレームを作成する」という、こちらは一度試したのですが採用しませんでした。採用しない理由といたしましては、部品列の要素として"箱"あるいは"ふくろ"しか持たない商品が存在しまして、商品まるごとフィルタされてしまったからです。説明足らずですみません。

    ですので、aggのラムダ関数のところで、['箱', 'ふくろ']と一致しない場合にcountし、Seriesをtupleやlistにappendすれば良いのでしょうか?
    こちらで試してみて、うまくいかなければまた質問させていただく存じますのでもうしばらく質問をオープンにしておきたいと思います。何卒宜しくお願い致します。

    キャンセル

  • 2019/04/01 09:10 編集

    「部品列の要素として"箱"あるいは"ふくろ"しか持たない商品が存在」
    なるほど。その場合は '部品項目数'は "0" 、'部品一覧'は "()" で良いのでしょうかね。

    とりあえず、その場合は 不要な行を削除してから``groupby()`` するのではなく、``groupby()`` してからその中で 不要な行を処理しないようにすることで対応できるかと思います。

    キャンセル

  • 2019/04/03 00:44

    お世話になります。確認遅くなりすみません。
    素晴らしいです。どちらのコードも求める通りの結果を得ることができました。そしてapply関数の方がわかりやすかったのでそちらを使用することにいたしました。applyでシリーズを返す関数を呼ぶなんてことができるんですね。
    わたしはpandasまだまだ勉強中で、抽出、並び替え、同じ値の一括代入、といった簡単なことしかできませんでしたので、これを使って色々出来そうなことが広がりそうでとても興奮しています・・!
    大変助かりました。ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.33%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • Pythonに関する質問
  • グループ毎の特定の列の要素数を条件付きで数えたい & グループ毎の特定の列の要素を文字列連結して新規列に代入したい