Додайте новий стовпчик до фрейму даних на основі словника


23

У мене є фрейм даних та словник. Мені потрібно додати новий кадр до фрейму даних і обчислити його значення на основі словника.

Машинне навчання, додавання нової функції на основі таблиці:

score = {(1, 45, 1, 1) : 4, (0, 1, 2, 1) : 5}
df = pd.DataFrame(data = {
    'gender' :      [1,  1,  0, 1,  1,  0,  0,  0,  1,  0],
    'age' :         [13, 45, 1, 45, 15, 16, 16, 16, 15, 15],
    'cholesterol' : [1,  2,  2, 1, 1, 1, 1, 1, 1, 1],
    'smoke' :       [0,  0,  1, 1, 7, 8, 3, 4, 4, 2]},
     dtype = np.int64)

print(df, '\n')
df['score'] = 0
df.score = score[(df.gender, df.age, df.cholesterol, df.smoke)]
print(df)

Я очікую наступного результату:

   gender  age  cholesterol  smoke    score
0       1   13            1      0      0 
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

Відповіді:


13

Оскільки scoreце словник (тому ключі унікальні), ми можемо використовувати MultiIndexвирівнювання

df = df.set_index(['gender', 'age', 'cholesterol', 'smoke'])
df['score'] = pd.Series(score)  # Assign values based on the tuple
df = df.fillna(0, downcast='infer').reset_index()  # Back to columns

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

1
Хороший один із MultiIIndex. Альтернатива: df['score'] =df.set_index(['gender', 'age', 'cholesterol', 'smoke']).index.map(score).fillna(0).to_numpy().
Куанг Хоанг

4
@ ALollz, пробач, я люблю ваші відповіді, але мені потрібно говорити, коли я бачу так багато відгуків щодо такої відповіді. Ця відповідь прекрасна і розумна. Але це не велико. Занадто багато рухомих частин для великого виграшу. У процесі ви створили нове dfчерез set_index, нове Seriesчерез конструктор. Хоча ви отримуєте перевагу вирівнювання індексу, коли призначаєте його df['score']. Нарешті, fillna(0, downcast='infer')виконується робота, але ніхто не повинен віддавати перевагу цьому тривалому рішенню зі створенням безлічі об'єктів панди без необхідності.
piRSquared

Знову вибачте, у вас є і моя позиція, я просто хочу направити людей на більш прості відповіді.
piRSquared

@piRSquared Я пішов на обід, і був здивований, що це привернув увагу, коли я повернувся. Я погоджуюся з тим, що трохи робити перекрут, щоб зробити щось, що mergeможе зробити простий . Я вважав, що відповідь буде опублікована швидко, тому я обрав альтернативу, і чомусь мав на увазі MultiIndices. Я погоджуюся, це, мабуть, не повинно бути прийнятою відповіддю, тому, сподіваємось, цього не станеться.
ALollz

1
Ой я з тобою. Я відповів те саме багато разів. Я просто роблю все можливе, щоб служити громаді (-: я вірю, що ви отримаєте мій намір.
piRSquared

7

Використовуючи assignрозуміння списку, отримуючи з scoreсловника набір значень (кожен рядок) , дефолт - до нуля, якщо його не знайдено.

>>> df.assign(score=[score.get(tuple(row), 0) for row in df.values])
   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

Хронометраж

Враховуючи різноманітність підходів, мені хотілося б порівняти деякі терміни.

# Initial dataframe 100k rows (10 rows of identical data replicated 10k times).
df = pd.DataFrame(data = {
    'gender' :      [1,  1,  0, 1,  1,  0,  0,  0,  1,  0] * 10000,
    'age' :         [13, 45, 1, 45, 15, 16, 16, 16, 15, 15] * 10000,
    'cholesterol' : [1,  2,  2, 1, 1, 1, 1, 1, 1, 1] * 10000,
    'smoke' :       [0,  0,  1, 1, 7, 8, 3, 4, 4, 2] * 10000},
     dtype = np.int64)

%timeit -n 10 df.assign(score=[score.get(tuple(v), 0) for v in df.values])
# 223 ms ± 9.28 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10 
df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
# 76.8 ms ± 2.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=[score.get(v, 0) for v in df.itertuples(index=False)])
# 113 ms ± 2.58 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit -n 10 df.assign(score=df.apply(lambda x: score.get(tuple(x), 0), axis=1))
# 1.84 s ± 77.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
(df
 .set_index(['gender', 'age', 'cholesterol', 'smoke'])
 .assign(score=pd.Series(score))
 .fillna(0, downcast='infer')
 .reset_index()
)
# 138 ms ± 11.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
s=pd.Series(score)
s.index.names=['gender','age','cholesterol','smoke']
df.merge(s.to_frame('score').reset_index(),how='left').fillna(0).astype(int)
# 24 ms ± 2.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=pd.Series(zip(df.gender, df.age, df.cholesterol, df.smoke))
                .map(score)
                .fillna(0)
                .astype(int))
# 191 ms ± 7.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=df[['gender', 'age', 'cholesterol', 'smoke']]
                .apply(tuple, axis=1)
                .map(score)
                .fillna(0))
# 1.95 s ± 134 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Мій улюблений трохи. Однак для того, щоб переконатися, що все залишається призначеного типу при обробці через те, що score.getя використовую itertuplesабо zip(*map(df.get, df))... Повторюю, це мій переважний підхід.
piRSquared

1
df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
piRSquared

1
Нарешті, більшість того, що я пишу, є красномовним, тому що хеш 1.0такий самий, як хеш, 1тому перегляд кортежів повинен спричинити ту саму відповідь незалежно. Вибачте @Alexander за стільки коментарів з цього приводу, але я просто хочу, щоб люди більше підтримували це, тому що ... вони повинні (-:
piRSquared

1
Поки ви терміни, подивіться на мою пропозицію. Бувають випадки, коли .valuesдорого
piRSquared

1
@AndyL. Ви навіть можете контролювати, які стовпці та в якому порядку: zip(*map(df.get, ['col2', 'col1', 'col5']))або отримувати кортежі модифікації df:zip(*map(df.eq(1).get, df))
piRSquared

4

Ви можете використовувати карту , оскільки оцінка є словником:

df['score'] = df[['gender', 'age', 'cholesterol', 'smoke']].apply(tuple, axis=1).map(score).fillna(0)
print(df)

Вихідні дані

   gender  age  cholesterol  smoke  score
0       1   13            1      0    0.0
1       1   45            2      0    0.0
2       0    1            2      1    5.0
3       1   45            1      1    4.0
4       1   15            1      7    0.0
5       0   16            1      8    0.0
6       0   16            1      3    0.0
7       0   16            1      4    0.0
8       1   15            1      4    0.0
9       0   15            1      2    0.0

В якості альтернативи ви можете використати розуміння списку:

df['score'] = [score.get(t, 0) for t in zip(df.gender, df.age, df.cholesterol, df.smoke)]
print(df)

Я хотів би розібратися зі своїм питанням. Дійсно, мені потрібно додати базу стовпця до діапазону значення стовпця. Наприклад, якщо 40 <вік <50, то оцінка = 4 і т.д. ... Тепер словник відображає точно певне значення. Те саме стосується і інших ключів ....
Микола

1
Додайте приклад того, що ви насправді хочете
Дані

Простий приклад: # Тут 40 і 50, 10 і 20 - це віковий діапазон, для якого я повинен використовувати бал = 4 (або 5) бал = {(1, 40, 50, 1, 1): 4, (0, 10, 20 , 1, 3): 5}
Микола

@Mikola Отже, якщо стать = 1 і 40 <вік <50 і так далі ...
Дані

1
@Mikola Ви повинні повідомити про це кожен орган, хоча в цей момент я вважаю, що краще, якщо ви задасте інше питання.
Dani Mesejo

4

Перелік розуміння та карта:

df['score'] = (pd.Series(zip(df.gender, df.age, df.cholesterol, df.smoke))
               .map(score)
               .fillna(0)
               .astype(int)
              )

Вихід:

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0
9       0   15            1      2    0.0

4

reindex

df['socre']=pd.Series(score).reindex(pd.MultiIndex.from_frame(df),fill_value=0).values
df
Out[173]: 
   gender  age  cholesterol  smoke  socre
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

Або merge

s=pd.Series(score)
s.index.names=['gender','age','cholesterol','smoke']
df=df.merge(s.to_frame('score').reset_index(),how='left').fillna(0)
Out[166]: 
   gender  age  cholesterol  smoke  score
0       1   13            1      0    0.0
1       1   45            2      0    0.0
2       0    1            2      1    5.0
3       1   45            1      1    4.0
4       1   15            1      7    0.0
5       0   16            1      8    0.0
6       0   16            1      3    0.0
7       0   16            1      4    0.0
8       1   15            1      4    0.0
9       0   15            1      2    0.0

2

Можливо, іншим способом буде використання .loc[]:

m=df.set_index(df.columns.tolist())
m.loc[list(score.keys())].assign(
           score=score.values()).reindex(m.index,fill_value=0).reset_index()

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

2

Просте рішення з одним рядком, Використовувати getта використовувати tupleрядки,

df['score'] = df.apply(lambda x: score.get(tuple(x), 0), axis=1)

Вищенаведене рішення передбачає, що стовпців, крім бажаних, немає в порядку. Якщо ні, просто використовуйте стовпці

cols = ['gender','age','cholesterol','smoke']
df['score'] = df[cols].apply(lambda x: score.get(tuple(x), 0), axis=1)

Використання score.getдобре. Однак, ви вважаєте за краще розуміння, на мою думку. Дивіться таймінги @ Олександра .
piRSquared

Гаразд @piSquared. Майте це на увазі.
Vishnudev
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.