Допустим, у меня есть фрейм данных (df), который выглядит следующим образом (точки обозначают больше столбцов):
Type Price1 Price2 Price3 Price4 Price5 ... ...
A nan 1 nan nan 2
A nan 3 nan nan 2
B nan nan 4 5 nan
B nan nan 6 7 nan
C nan 2 nan nan 1
C nan 4 nan nan 3
D 1 8 nan nan nan
D 9 6 nan nan nan
Мне нужно преобразовать этот фрейм данных, чтобы он выглядел так:
Type newcol1 newcol2 ... ...
A 2 1
A 2 3
B 4 5
B 6 7
C 1 2
C 3 4
D 1 8
D 9 6
Это критерии для выбора того, какие столбцы получат какие значения:
if type == 'A'
'Price5' -> 'newcol1' && 'Price2' -> 'newcol2'
if type == 'B'
'Price3' -> 'newcol1' && 'Price4' -> 'newcol2'
if type == 'C'
'Price5' -> 'newcol1' && 'Price2' -> 'newcol2'
if type == 'D'
'Price1' -> 'newcol1' && 'Price2' -> 'newcol2'
Моя логика заключалась в том, чтобы использовать функцию маски для достижения этой цели и присвоить значения двум столбцам, а затем просто переименовать их, что работает, но я обрабатываю каждый случай отдельно и это немного беспорядочно:
df = df.mask(df['Type'].eq('A'), df.assign(**{'Price3': df['Price5'].values, 'Price4': df['Price2'].values}))
df.rename(columns = {'Price3':'newcol1'}, inplace = True)
df.rename(columns = {'Price4':'newcol2'}, inplace = True)
Как я могу добиться этого более эффективным способом?
Вы можете использовать собственный groupby.apply с переименованием и переиндексацией:
# dictionary to define the mappings per group
dic = {'A': {'Price5': 'newcol1', 'Price2': 'newcol2'},
'B': {'Price3': 'newcol1', 'Price4': 'newcol2'},
'C': {'Price5': 'newcol1', 'Price2': 'newcol2'},
'D': {'Price1' :'newcol1', 'Price2': 'newcol2'},
}
def f(g):
# for each group, rename and select the columns
d = dic.get(g.name, {})
return (g.rename(columns=d)
.reindex(columns=d.values())
)
# apply the function per group
out = df.groupby('Type').apply(f).reset_index(0)
Выход:
Type newcol1 newcol2
0 A 2.0 1.0
1 A 2.0 3.0
2 B 4.0 5.0
3 B 6.0 7.0
4 C 1.0 2.0
5 C 3.0 4.0
6 D 1.0 8.0
7 D 9.0 6.0
Вы можете использовать лямбда-функцию:
df['newCol1'] = df.apply(lambda x: x.Price5 if x.Type == 'A' else (x.Price3 if x.Type == 'B' else (x.Price5 if x.Type == 'C' else (x.Price1 if x.Type=='D' else 'NaN'))), axis=1)
df['newCol2'] = df.apply(lambda x: x.Price2 if x.Type == 'A' else (x.Price4 if x.Type == 'B' else (x.Price2 if x.Type == 'C' else (x.Price2 if x.Type=='D' else 'NA'))), axis=1)
Вы можете использовать np.select().
Сначала определите желаемое сопоставление. Я использую фрейм данных:
mapping = pd.DataFrame.from_dict(
{
'A': {'newcol1': 'Price5', 'newcol2': 'Price2'},
'B': {'newcol1': 'Price3', 'newcol2': 'Price4'},
'C': {'newcol1': 'Price5', 'newcol2': 'Price2'},
'D': {'newcol1': 'Price1', 'newcol2': 'Price2'}},
orient='index')
newcol1 newcol2
A Price5 Price2
B Price3 Price4
C Price5 Price2
D Price1 Price2
Затем создайте список условий:
cond = [df['Type'].eq(k) for k in mapping.index]
Наконец, используйте np.select()
:
# For this demo, I'm outputting to a new dataframe:
df_out = df['Type'].to_frame()
for new_col in mapping.columns:
df_out[new_col] = np.select(
cond,
[df[old_col] for old_col in mapping[new_col]]
).astype('int')
Type newcol1 newcol2
0 A 2 1
1 A 2 3
2 B 4 5
3 B 6 7
4 C 1 2
5 C 3 4
6 D 1 8
7 D 9 6
P.S. pd.Series.case_when() действительно похож на np.select()
, но предназначен для замены значений, поэтому я бы не стал использовать его здесь, если только вы не считаете один из old_col
значением по умолчанию для каждого new_col
.
'NaN'
и'NA'
не являются числовыми, поэтому они испортят dtype. Используйтеnp.nan
.