在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
Map某些列,同时保持其他列不变,在方法链样式中最简单的方法是什么?
我已经可以使用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
)
我可以使用transform,但是transform会删除(!)未提及的列。Transform对其他列使用passthrough比较理想:
# 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...
)
有没有一种“最佳”的方法可以流畅地对某些列应用转换,并传递其他列?
编辑:在这一行下面,是阅读劳伦特的想法后的建议。
添加一个helper函数,该函数允许仅对一列应用Map:
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
2条答案
按热度按时间r1zk6ea11#
以下是我对你这个有趣问题的看法。
在Pandas中,我不知道还有比组合pipe、assign或transform更惯用的方法来进行方法链接,但我知道 *“对其他列使用带passthrough的transform将是理想的”。
因此,我建议将它与一个高阶函数一起使用来处理其他列,通过利用Python标准库functools模块来进行更类似函数的编码。
例如,使用以下玩具 Dataframe :
可定义以下部分对象:
然后使用
transform
执行各种链赋值,例如:iklwldmw2#
如果我理解正确的话,在assign中使用
**
可能会有帮助,例如,如果你只想使用pd.to_numeric
转换数字数据类型,下面的代码应该可以工作。通过解压缩df,你实际上是给assign赋值每列所需的值,这相当于为每列写
sepal_length = pd.to_numeric(df['sepal_length'],errors='coerce'), sepal_width = ...
。