【发布时间】:2022-06-21 13:36:43
【问题描述】:
在 Pandas 中转换列最流畅(或易于阅读)method chaining 的解决方案是什么?
(“方法链接”或“流利的”是coding style made popular by Tom Augspurger 等等。)
为了示例,我们设置一些示例数据:
import pandas as pd
import seaborn as sns
df = sns.load_dataset("iris").astype(str) # Just for this example
df.loc[1, :] = "NA"
df.head()
#
# sepal_length sepal_width petal_length petal_width species
# 0 5.1 3.5 1.4 0.2 setosa
# 1 NA NA NA NA NA
# 2 4.7 3.2 1.3 0.2 setosa
# 3 4.6 3.1 1.5 0.2 setosa
# 4 5.0 3.6 1.4 0.2 setosa
仅针对此示例:我想通过函数映射某些列 - sepal_length 使用 pd.to_numeric - 同时保持其他列不变。在方法链样式中最简单的方法是什么?
我已经可以使用assign,但我在这里重复列名,这是我不想要的。
new_result = (
df.assign(sepal_length = lambda df_: pd.to_numeric(df_.sepal_length, errors="coerce"))
.head() # Further chaining methods, what it may be
)
我可以使用变换,但变换会丢弃(!)未提及的列。对其他列进行直通转换是理想的:
# Columns not mentioned in transform are lost
new_result = (
df.transform({'sepal_length': lambda series: pd.to_numeric(series, errors="coerce")})
.head() # Further chaining methods...
)
是否有一种“最佳”方式以流畅的风格将转换应用于某些列,并传递其他列?
编辑:在此行下方,阅读 Laurent 的想法后提出的建议。
添加一个帮助函数,允许仅将映射应用于一列:
import functools
coerce_numeric = functools.partial(pd.to_numeric, errors='coerce')
def on_column(column, mapping):
"""
Adaptor that takes a column transformation and returns a "whole dataframe" function suitable for .pipe()
Notice that columns take the name of the returned series, if applicable
Columns mapped to None are removed from the result.
"""
def on_column_(df):
df = df.copy(deep=False)
res = mapping(df[column])
# drop column if mapped to None
if res is None:
df.pop(column)
return df
df[column] = res
# update column name if mapper changes its name
if hasattr(res, 'name') and res.name != col:
df = df.rename(columns={column: res.name})
return df
return on_column_
这现在允许在前面的示例中进行以下整洁的链接:
new_result = (
df.pipe(on_column('sepal_length', coerce_numeric))
.head() # Further chaining methods...
)
但是,我仍然愿意在没有胶水代码的情况下仅在原生 pandas 中执行此操作。
编辑 2 以进一步适应 Laurent 的想法,作为替代方案。独立示例:
import pandas as pd
df = pd.DataFrame(
{"col1": ["4", "1", "3", "2"], "col2": [9, 7, 6, 5], "col3": ["w", "z", "x", "y"]}
)
def map_columns(mapping=None, /, **kwargs):
"""
Transform the specified columns and let the rest pass through.
Examples:
df.pipe(map_columns(a=lambda x: x + 1, b=str.upper))
# dict for non-string column names
df.pipe({(0, 0): np.sqrt, (0, 1): np.log10})
"""
if mapping is not None and kwargs:
raise ValueError("Only one of a dict and kwargs can be used at the same time")
mapping = mapping or kwargs
def map_columns_(df: pd.DataFrame) -> pd.DataFrame:
mapping_funcs = {**{k: lambda x: x for k in df.columns}, **mapping}
# preserve original order of columns
return df.transform({key: mapping_funcs[key] for key in df.columns})
return map_columns_
df2 = (
df
.pipe(map_columns(col2=pd.to_numeric))
.sort_values(by="col1")
.pipe(map_columns(col1=lambda x: x.astype(str) + "0"))
.pipe(map_columns({'col2': lambda x: -x, 'col3': str.upper}))
.reset_index(drop=True)
)
df2
# col1 col2 col3
# 0 10 -7 Z
# 1 20 -5 Y
# 2 30 -6 X
# 3 40 -9 W
【问题讨论】:
-
this 问题和几个答案有帮助吗?
-
谢谢,但它并没有真正解决一般的流畅列转换
-
我以为这就是您要的。将函数(例如 pd.to_numeric)应用于多个列(特定列,但不是全部)。在我发送的链接中,他们确实做到了这一点,要么列出要选择的列,然后使用带有axis = 1的
df[cols].apply或col in cols,或者他们将相同类型的列组合在一起。在其他列维护的所有解决方案中。也许我没有得到“流利的列转换”这个词,那么我很抱歉:P -
我为方法链添加了一个链接。以这种方式找到解决方案是问题的主要焦点。我知道如何解决一般问题,您链接中的解决方案肯定有帮助,但不是我所追求的。