Prioritätsbasierte Kategorisierung mit Pandas / Python

Sep 16 2020

Ich habe rechnungsbezogene Daten im folgenden Datenrahmen und Codelisten

df = pd.DataFrame({
    'invoice':[1,1,2,2,2,3,3,3,4,4,4,5,5,6,6,6,7],
    'code':[101,104,105,101,106,106,104,101,104,105,111,109,111,110,101,114,112],
    'qty':[2,1,1,3,2,4,7,1,1,1,1,4,2,1,2,2,1]
})

+---------+------+-----+
| invoice | code | qty |
+---------+------+-----+
|    1    |  101 |  2  |
+---------+------+-----+
|    1    |  104 |  1  |
+---------+------+-----+
|    2    |  105 |  1  |
+---------+------+-----+
|    2    |  101 |  3  |
+---------+------+-----+
|    2    |  106 |  2  |
+---------+------+-----+
|    3    |  106 |  4  |
+---------+------+-----+
|    3    |  104 |  7  |
+---------+------+-----+
|    3    |  101 |  1  |
+---------+------+-----+
|    4    |  104 |  1  |
+---------+------+-----+
|    4    |  105 |  1  |
+---------+------+-----+
|    4    |  111 |  1  |
+---------+------+-----+
|    5    |  109 |  4  |
+---------+------+-----+
|    5    |  111 |  2  |
+---------+------+-----+
|    6    |  110 |  1  |
+---------+------+-----+
|    6    |  101 |  2  |
+---------+------+-----+
|    6    |  114 |  2  |
+---------+------+-----+
|    7    |  104 |  2  |
+---------+------+-----+

Codelisten sind,

Soda =  [101,102]
Hot =  [103,109]
Juice =  [104,105]
Milk =  [106,107,108]
Dessert =  [110,111]

Meine Aufgabe ist es, eine neue categorySpalte basierend auf den unten angegebenen hinzuzufügen Order of Priority.

  1. Priorität Nr. 1: Wenn eine Rechnung mehr als 10 Stück hat, sollte sie als kategorisiert werden Mega. zB: Summe qtyvoninvoice 3 is 12

  2. Priorität Nr rest of the invoice. 2: von der . Wenn codeeines der invoiceElemente in der MilkListe enthalten ist, sollte die Kategorie lauten Healthy. zB: in invoice 2 code 106ist in Milk. Daher wird die vollständige Rechnung als kategorisiert Healthy. Unabhängig von anderen Artikeln ( code 101 & 105) sind in der Rechnung vorhanden. Da Prioritäten auf die fullRechnung angewendet werden .

  3. Priorität Nr. 3: Aus der rest of the invoice, wenn eine codeder invoicein der JuiceListe ist, dann hat dies2 parts

(3.1) Wenn die Summe dieser Saftmenge ist equal to 1, sollte die Kategorie sein OneJuice. zB: invoice 1hat code 104und qty 1.dies invoice 1wird OneJuiceunabhängig davon, ob andere Artikel ( code 101) in der Rechnung vorhanden sind. Da Prioritäten auf die fullRechnung angewendet werden .

(3.2) Wenn die Summe dieser Saftmenge ist greater than 1, sollte die Kategorie sein ManyJuice. zB: invoice 4hat code 104 & 105 und qty 1 + 1 = 2.

  1. Priorität Nr. 4: rest of the invoiceWenn eine codeder Rechnungen in der HotListe enthalten ist, sollte sie als kategorisiert werden HotLovers. Unabhängig von anderen Artikeln sind in der Rechnung vorhanden.

  2. Priorität Nr. 5: rest of the invoiceWenn eine codeder Rechnungen in der DessertListe enthalten ist, sollte sie als kategorisiert werden DessertLovers.

  3. Schließlich sollte der Rest der Rechnung als kategorisiert werden Others.

Meine gewünschte Ausgabe ist wie folgt.

+---------+------+-----+---------------+
| invoice | code | qty |    category   |
+---------+------+-----+---------------+
|    1    |  101 |  2  |    OneJuice   |
+---------+------+-----+---------------+
|    1    |  104 |  1  |    OneJuice   |
+---------+------+-----+---------------+
|    2    |  105 |  1  |    Healthy    |
+---------+------+-----+---------------+
|    2    |  101 |  3  |    Healthy    |
+---------+------+-----+---------------+
|    2    |  106 |  2  |    Healthy    |
+---------+------+-----+---------------+
|    3    |  106 |  4  |      Mega     |
+---------+------+-----+---------------+
|    3    |  104 |  7  |      Mega     |
+---------+------+-----+---------------+
|    3    |  101 |  1  |      Mega     |
+---------+------+-----+---------------+
|    4    |  104 |  1  |   ManyJuice   |
+---------+------+-----+---------------+
|    4    |  105 |  1  |   ManyJuice   |
+---------+------+-----+---------------+
|    4    |  111 |  1  |   ManyJuice   |
+---------+------+-----+---------------+
|    5    |  109 |  4  |   HotLovers   |
+---------+------+-----+---------------+
|    5    |  111 |  2  |   HotLovers   |
+---------+------+-----+---------------+
|    6    |  110 |  1  | DessertLovers |
+---------+------+-----+---------------+
|    6    |  101 |  2  | DessertLovers |
+---------+------+-----+---------------+
|    6    |  114 |  2  | DessertLovers |
+---------+------+-----+---------------+
|    7    |  104 |  2  |     ManyJuice |
+---------+------+-----+---------------+

Bisher habe ich unten versucht. Es klappt. aber ziemlich naiv und überhaupt nicht pythonisch. Auch wenn ich dies auf das ursprüngliche Dataset angewendet habe, ist der Code sehr, sehr langsam.

# Calculating Priority No.1 
L = df.groupby(['invoice'])['qty'].transform('sum') >= 10
df_Large = df[L]['invoice'].to_frame()
df_Large['category'] = 'Mega'
df_Large.drop_duplicates(['invoice'], inplace=True)


# Calculating Priority No.2
df_1 = df[~L] # removing Priority No.1 calculated above
M = (df_1['code'].isin(Milk)
.groupby(df_1['invoice'])
.transform('any'))
df_Milk = df_1[M]['invoice'].to_frame()
df_Milk['category'] = 'Healthy'
df_Milk.drop_duplicates(['invoice'], inplace=True)

# Calculating Priority No.3

# 3.a Part -1

df_2 = df[~L & ~M]  # removing Priority No.1 & 2 calculated above
J_1 = (df_2['code'].isin(Juice)
.groupby(df_2['invoice'])
.transform('sum') == 1)
df_SM = df_2[J_1]['invoice'].to_frame()
df_SM['category'] = 'OneJuice'
df_SM.drop_duplicates(['invoice'], inplace=True)


# 3.b Part -2
J_2 = (df_2['code'].isin(Juice)
.groupby(df_2['invoice'])
.transform('sum') > 1)
df_MM = df_2[J_2]['invoice'].to_frame()
df_MM['category'] = 'ManyJuice'
df_MM.drop_duplicates(['invoice'], inplace=True)


# Calculating Priority No.4
df_3 = df[~L & ~M & ~J_1 & ~J_2]  # removing Priority No.1, 2 & 3 (a & b) calculated above
H = (df_3['code'].isin(Hot)
.groupby(df_3['invoice'])
.transform('any'))
df_Hot = df_3[H]['invoice'].to_frame()
df_Hot['category'] = 'HotLovers'
df_Hot.drop_duplicates(['invoice'], inplace=True)


# Calculating Priority No.5
df_4 = df[~L & ~M & ~J_1 & ~J_2 & ~H ] # removing Priority No.1, 2, 3 (a & b) and 4 calculated above
D = (df_4['code'].isin(Dessert)
.groupby(df_4['invoice'])
.transform('any'))
df_Dessert = df_4[D]['invoice'].to_frame()
df_Dessert['category'] = 'DessertLovers'
df_Dessert.drop_duplicates(['invoice'], inplace=True)

# merge all dfs
category = pd.concat([df_Large,df_Milk,df_SM,df_MM,df_Hot,df_Dessert], axis=0,sort=False, ignore_index=True)

# Final merge to the original dataset
df = df.merge(category,on='invoice', how='left').fillna(value='Others')

Benötigen Sie also Hilfe, um diesen Code für Geschwindigkeit / Effizienz und pythonische Weise zu bereinigen.

Antworten

3 MichaelSzczesny Sep 18 2020 at 07:17

Sie können versuchen, np.select zu verwenden

df['category'] = np.select([
    df.groupby('invoice')['qty'].transform('sum') >= 10,
    df['code'].isin(Milk).groupby(df.invoice).transform('any'),
    (df['qty']*df['code'].isin(Juice)).groupby(df.invoice).transform('sum') == 1,
    (df['qty']*df['code'].isin(Juice)).groupby(df.invoice).transform('sum') > 1,
    df['code'].isin(Hot).groupby(df.invoice).transform('any'),
    df['code'].isin(Dessert).groupby(df.invoice).transform('any')
],
    ['Mega','Healthy','OneJuice','ManyJuice','HotLovers','DessertLovers'],
    'Other'
)
print(df)

Ausgabe

    invoice  code  qty       category
0         1   101    2       OneJuice
1         1   104    1       OneJuice
2         2   105    1        Healthy
3         2   101    3        Healthy
4         2   106    2        Healthy
5         3   106    4           Mega
6         3   104    7           Mega
7         3   101    1           Mega
8         4   104    1      ManyJuice
9         4   105    1      ManyJuice
10        4   111    1      ManyJuice
11        5   109    4      HotLovers
12        5   111    2      HotLovers
13        6   110    1  DessertLovers
14        6   101    2  DessertLovers
15        6   114    2  DessertLovers
16        7   104    2      ManyJuice

Mikro-Benchmark

pd.show_versions()

commit           : None
python           : 3.7.5.final.0
python-bits      : 64
OS               : Linux
OS-release       : 4.4.0-18362-Microsoft
machine          : x86_64
processor        : x86_64
byteorder        : little
LC_ALL           : None
LANG             : C.UTF-8
LOCALE           : en_US.UTF-8

pandas           : 0.25.3
numpy            : 1.17.4

Daten wurden mit erstellt

def make_data(n):
     return pd.DataFrame({
    'invoice':np.arange(n)//3,
    'code':np.random.choice(np.arange(101,112),n),
    'qty':np.random.choice(np.arange(1,8), n, p=[10/25,10/25,1/25,1/25,1/25,1/25,1/25])
})

Ergebnisse

perfplot.show(
    setup=make_data,
    kernels=[get_category, get_with_np_select],
    n_range=[2**k for k in range(8, 20)],
    logx=True,
    logy=True,
    equality_check=False,
    xlabel='len(df)')