Pandas 2.2 中文官方教程和指南(二十三)(2)https://developer.aliyun.com/article/1508853
使用 ndarray
在重新分析时,时间花在从每一行创建一个Series
,并且从索引和系列中调用__getitem__
(每行三次)。这些 Python 函数调用很昂贵,可以通过传递一个np.ndarray
来改进。
In [12]: %prun -l 4 df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 52533 function calls (52515 primitive calls) in 0.019 seconds Ordered by: internal time List reduced from 161 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 3000 0.003 0.000 0.012 0.000 series.py:1095(__getitem__) 3000 0.002 0.000 0.005 0.000 series.py:1220(_get_value) 3000 0.002 0.000 0.002 0.000 base.py:3777(get_loc) 3000 0.002 0.000 0.002 0.000 indexing.py:2765(check_dict_or_set_indexers)
In [13]: %%cython ....: cimport numpy as np ....: import numpy as np ....: cdef double f_typed(double x) except? -2: ....: return x * (x - 1) ....: cpdef double integrate_f_typed(double a, double b, int N): ....: cdef int i ....: cdef double s, dx ....: s = 0 ....: dx = (b - a) / N ....: for i in range(N): ....: s += f_typed(a + i * dx) ....: return s * dx ....: cpdef np.ndarray[double] apply_integrate_f(np.ndarray col_a, np.ndarray col_b, ....: np.ndarray col_N): ....: assert (col_a.dtype == np.float64 ....: and col_b.dtype == np.float64 and col_N.dtype == np.dtype(int)) ....: cdef Py_ssize_t i, n = len(col_N) ....: assert (len(col_a) == len(col_b) == n) ....: cdef np.ndarray[double] res = np.empty(n) ....: for i in range(len(col_a)): ....: res[i] = integrate_f_typed(col_a[i], col_b[i], col_N[i]) ....: return res ....: Content of stderr: In file included from /home/runner/micromamba/envs/test/lib/python3.10/site-packages/numpy/core/include/numpy/ndarraytypes.h:1929, from /home/runner/micromamba/envs/test/lib/python3.10/site-packages/numpy/core/include/numpy/ndarrayobject.h:12, from /home/runner/micromamba/envs/test/lib/python3.10/site-packages/numpy/core/include/numpy/arrayobject.h:5, from /home/runner/.cache/ipython/cython/_cython_magic_96d1519457caba8fa4f96b759be00659f51c6b18.c:1215: /home/runner/micromamba/envs/test/lib/python3.10/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning "Using deprecated NumPy API, disable it with " "#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" [-Wcpp] 17 | #warning "Using deprecated NumPy API, disable it with " \ | ^~~~~~~
这个实现创建了一个零数组,并插入了对每一行应用integrate_f_typed
的结果。在 Cython 中循环遍历ndarray
比循环遍历Series
对象更快。
由于apply_integrate_f
被定义为接受np.ndarray
,因此需要调用Series.to_numpy()
来利用这个函数。
In [14]: %timeit apply_integrate_f(df["a"].to_numpy(), df["b"].to_numpy(), df["N"].to_numpy()) 834 us +- 4.04 us per loop (mean +- std. dev. of 7 runs, 1,000 loops each)
性能比以前的实现提高了近十倍。
禁用编译器指令
现在大部分时间都花在了apply_integrate_f
上。禁用 Cython 的boundscheck
和wraparound
检查可以提高性能。
In [15]: %prun -l 4 apply_integrate_f(df["a"].to_numpy(), df["b"].to_numpy(), df["N"].to_numpy()) 78 function calls in 0.001 seconds Ordered by: internal time List reduced from 21 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1 0.001 0.001 0.001 0.001 <string>:1(<module>) 1 0.000 0.000 0.001 0.001 {built-in method builtins.exec} 3 0.000 0.000 0.000 0.000 frame.py:4062(__getitem__) 3 0.000 0.000 0.000 0.000 base.py:541(to_numpy)
In [16]: %%cython ....: cimport cython ....: cimport numpy as np ....: import numpy as np ....: cdef np.float64_t f_typed(np.float64_t x) except? -2: ....: return x * (x - 1) ....: cpdef np.float64_t integrate_f_typed(np.float64_t a, np.float64_t b, np.int64_t N): ....: cdef np.int64_t i ....: cdef np.float64_t s = 0.0, dx ....: dx = (b - a) / N ....: for i in range(N): ....: s += f_typed(a + i * dx) ....: return s * dx ....: @cython.boundscheck(False) ....: @cython.wraparound(False) ....: cpdef np.ndarray[np.float64_t] apply_integrate_f_wrap( ....: np.ndarray[np.float64_t] col_a, ....: np.ndarray[np.float64_t] col_b, ....: np.ndarray[np.int64_t] col_N ....: ): ....: cdef np.int64_t i, n = len(col_N) ....: assert len(col_a) == len(col_b) == n ....: cdef np.ndarray[np.float64_t] res = np.empty(n, dtype=np.float64) ....: for i in range(n): ....: res[i] = integrate_f_typed(col_a[i], col_b[i], col_N[i]) ....: return res ....: Content of stderr: In file included from /home/runner/micromamba/envs/test/lib/python3.10/site-packages/numpy/core/include/numpy/ndarraytypes.h:1929, from /home/runner/micromamba/envs/test/lib/python3.10/site-packages/numpy/core/include/numpy/ndarrayobject.h:12, from /home/runner/micromamba/envs/test/lib/python3.10/site-packages/numpy/core/include/numpy/arrayobject.h:5, from /home/runner/.cache/ipython/cython/_cython_magic_3bb7bde31cdaf5ab952bfe5a612c6edef03550d0.c:1216: /home/runner/micromamba/envs/test/lib/python3.10/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning "Using deprecated NumPy API, disable it with " "#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" [-Wcpp] 17 | #warning "Using deprecated NumPy API, disable it with " \ | ^~~~~~~
In [17]: %timeit apply_integrate_f_wrap(df["a"].to_numpy(), df["b"].to_numpy(), df["N"].to_numpy()) 620 us +- 2.65 us per loop (mean +- std. dev. of 7 runs, 1,000 loops each)
然而,循环索引器i
访问数组中的无效位置会导致段错误,因为内存访问没有被检查。有关boundscheck
和wraparound
的更多信息,请参阅 Cython 文档中关于编译器指令的部分。
Numba(即时编译)
一个替代静态编译 Cython 代码的方法是使用动态即时(JIT)编译器Numba。
Numba 允许您编写一个纯 Python 函数,可以通过使用@jit
装饰将其 JIT 编译为本机机器指令,性能类似于 C、C++和 Fortran。
Numba 通过在导入时、运行时或静态(使用包含的 pycc 工具)生成优化的机器代码来工作。Numba 支持将 Python 编译为在 CPU 或 GPU 硬件上运行,并设计用于与 Python 科学软件堆栈集成。
注意
@jit
编译会增加函数运行时的开销,因此在使用小数据集时可能无法实现性能优势。考虑缓存你的函数,以避免每次运行函数时的编译开销。
Numba 可以在 pandas 中以两种方式使用:
- 在选择的 pandas 方法中指定
engine="numba"
关键字 - 定义自己的 Python 函数,并用
@jit
装饰,将Series
或DataFrame
的底层 NumPy 数组(使用Series.to_numpy()
)传递给函数。
pandas Numba 引擎
如果已安装 Numba,可以在选择 pandas 方法中指定 engine="numba"
来使用 Numba 执行该方法。支持 engine="numba"
的方法还将具有一个 engine_kwargs
关键字,接受一个字典,允许指定 "nogil"
、"nopython"
和 "parallel"
键以及布尔值传递给 @jit
装饰器。如果未指定 engine_kwargs
,则默认为 {"nogil": False, "nopython": True, "parallel": False}
,除非另有规定。
注意
就性能而言,使用 Numba 引擎运行函数的第一次将会很慢,因为 Numba 将有一些函数编译开销。然而,JIT 编译的函数会被缓存,后续调用将会很快。一般来说,Numba 引擎在大量数据点(例如 100 万以上)上表现良好。
In [1]: data = pd.Series(range(1_000_000)) # noqa: E225 In [2]: roll = data.rolling(10) In [3]: def f(x): ...: return np.sum(x) + 5 # Run the first time, compilation time will affect performance In [4]: %timeit -r 1 -n 1 roll.apply(f, engine='numba', raw=True) 1.23 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each) # Function is cached and performance will improve In [5]: %timeit roll.apply(f, engine='numba', raw=True) 188 ms ± 1.93 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) In [6]: %timeit roll.apply(f, engine='cython', raw=True) 3.92 s ± 59 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
如果您的计算硬件包含多个 CPU,将 parallel
设置为 True
可以实现最大的性能提升,以利用多个 CPU。在内部,pandas 利用 numba 对 DataFrame
的列进行并行计算;因此,这种性能优势仅对具有大量列的 DataFrame
有益。
In [1]: import numba In [2]: numba.set_num_threads(1) In [3]: df = pd.DataFrame(np.random.randn(10_000, 100)) In [4]: roll = df.rolling(100) In [5]: %timeit roll.mean(engine="numba", engine_kwargs={"parallel": True}) 347 ms ± 26 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [6]: numba.set_num_threads(2) In [7]: %timeit roll.mean(engine="numba", engine_kwargs={"parallel": True}) 201 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
自定义函数示例
通过使用 Series.to_numpy()
将 pandas 对象的 NumPy 数组表示传递给自定义 Python 函数,并使用 @jit
装饰器可以与 pandas 对象一起使用。
import numba @numba.jit def f_plain(x): return x * (x - 1) @numba.jit def integrate_f_numba(a, b, N): s = 0 dx = (b - a) / N for i in range(N): s += f_plain(a + i * dx) return s * dx @numba.jit def apply_integrate_f_numba(col_a, col_b, col_N): n = len(col_N) result = np.empty(n, dtype="float64") assert len(col_a) == len(col_b) == n for i in range(n): result[i] = integrate_f_numba(col_a[i], col_b[i], col_N[i]) return result def compute_numba(df): result = apply_integrate_f_numba( df["a"].to_numpy(), df["b"].to_numpy(), df["N"].to_numpy() ) return pd.Series(result, index=df.index, name="result")
In [4]: %timeit compute_numba(df) 1000 loops, best of 3: 798 us per loop
在这个例子中,使用 Numba 比 Cython 更快。
Numba 还可以用于编写不需要用户显式循环观察向量的向量化函数;向量化函数将自动应用于每一行。考虑以下示例,将每个观察值加倍:
import numba def double_every_value_nonumba(x): return x * 2 @numba.vectorize def double_every_value_withnumba(x): # noqa E501 return x * 2
# Custom function without numba In [5]: %timeit df["col1_doubled"] = df["a"].apply(double_every_value_nonumba) # noqa E501 1000 loops, best of 3: 797 us per loop # Standard implementation (faster than a custom function) In [6]: %timeit df["col1_doubled"] = df["a"] * 2 1000 loops, best of 3: 233 us per loop # Custom function with numba In [7]: %timeit df["col1_doubled"] = double_every_value_withnumba(df["a"].to_numpy()) 1000 loops, best of 3: 145 us per loop
注意事项
Numba 最擅长加速将数值函数应用于 NumPy 数组的函数。如果尝试对包含不受支持的 Python 或 NumPy 代码的函数进行 @jit
,编译将会回退到 object mode,这很可能不会加速函数。如果希望 Numba 在无法编译函数以加速代码时抛出错误,请传递参数 nopython=True
给 Numba(例如 @jit(nopython=True)
)。有关故障排除 Numba 模式的更多信息,请参阅 Numba 故障排除页面。
使用parallel=True
(例如@jit(parallel=True)
)可能会导致SIGABRT
,如果线程层导致不安全行为。您可以在使用parallel=True
运行 JIT 函数之前,首先指定一个安全的线程层。
通常,如果在使用 Numba 时遇到段错误(SIGSEGV
),请将问题报告给Numba 问题跟踪器。
pandas Numba 引擎
如果安装了 Numba,可以在选择 pandas 方法中指定engine="numba"
以使用 Numba 执行该方法。支持engine="numba"
的方法还将具有一个engine_kwargs
关键字,接受一个字典,允许指定"nogil"
、"nopython"
和"parallel"
键及其布尔值传递给@jit
装饰器。如果未指定engine_kwargs
,则默认为{"nogil": False, "nopython": True, "parallel": False}
,除非另有规定。
注意
就性能而言,使用 Numba 引擎运行函数的第一次将会很慢,因为 Numba 将有一些函数编译开销。然而,JIT 编译的函数会被缓存,后续调用将会很快。一般来说,Numba 引擎在大量数据点(例如 100 万个以上)上表现良好。
In [1]: data = pd.Series(range(1_000_000)) # noqa: E225 In [2]: roll = data.rolling(10) In [3]: def f(x): ...: return np.sum(x) + 5 # Run the first time, compilation time will affect performance In [4]: %timeit -r 1 -n 1 roll.apply(f, engine='numba', raw=True) 1.23 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each) # Function is cached and performance will improve In [5]: %timeit roll.apply(f, engine='numba', raw=True) 188 ms ± 1.93 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) In [6]: %timeit roll.apply(f, engine='cython', raw=True) 3.92 s ± 59 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
如果您的计算硬件包含多个 CPU,通过将parallel
设置为True
可以实现最大的性能提升,以利用多个 CPU。在内部,pandas 利用 numba 来并行计算DataFrame
的列;因此,这种性能优势仅对具有大量列的DataFrame
有益。
In [1]: import numba In [2]: numba.set_num_threads(1) In [3]: df = pd.DataFrame(np.random.randn(10_000, 100)) In [4]: roll = df.rolling(100) In [5]: %timeit roll.mean(engine="numba", engine_kwargs={"parallel": True}) 347 ms ± 26 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [6]: numba.set_num_threads(2) In [7]: %timeit roll.mean(engine="numba", engine_kwargs={"parallel": True}) 201 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
自定义函数示例
通过使用@jit
装饰的自定义 Python 函数,可以通过Series.to_numpy()
将它们的 NumPy 数组表示传递给 pandas 对象。
import numba @numba.jit def f_plain(x): return x * (x - 1) @numba.jit def integrate_f_numba(a, b, N): s = 0 dx = (b - a) / N for i in range(N): s += f_plain(a + i * dx) return s * dx @numba.jit def apply_integrate_f_numba(col_a, col_b, col_N): n = len(col_N) result = np.empty(n, dtype="float64") assert len(col_a) == len(col_b) == n for i in range(n): result[i] = integrate_f_numba(col_a[i], col_b[i], col_N[i]) return result def compute_numba(df): result = apply_integrate_f_numba( df["a"].to_numpy(), df["b"].to_numpy(), df["N"].to_numpy() ) return pd.Series(result, index=df.index, name="result")
In [4]: %timeit compute_numba(df) 1000 loops, best of 3: 798 us per loop
在这个例子中,使用 Numba 比 Cython 更快。
Numba 还可以用于编写矢量化函数,无需用户显式循环遍历向量的观测值;矢量化函数将自动应用于每一行。考虑以下示例,将每个观测值加倍:
import numba def double_every_value_nonumba(x): return x * 2 @numba.vectorize def double_every_value_withnumba(x): # noqa E501 return x * 2
# Custom function without numba In [5]: %timeit df["col1_doubled"] = df["a"].apply(double_every_value_nonumba) # noqa E501 1000 loops, best of 3: 797 us per loop # Standard implementation (faster than a custom function) In [6]: %timeit df["col1_doubled"] = df["a"] * 2 1000 loops, best of 3: 233 us per loop # Custom function with numba In [7]: %timeit df["col1_doubled"] = double_every_value_withnumba(df["a"].to_numpy()) 1000 loops, best of 3: 145 us per loop
注意事项
Numba 最擅长加速将数值函数应用于 NumPy 数组的函数。如果尝试@jit
一个包含不受支持的Python或NumPy代码的函数,编译将会回到对象模式,这通常不会加速您的函数。如果希望 Numba 在无法编译函数以加快代码速度时抛出错误,请向 Numba 传递参数nopython=True
(例如,@jit(nopython=True)
)。有关 Numba 模式故障排除的更多信息,请参阅Numba 故障排除页面。
使用parallel=True
(例如,@jit(parallel=True)
)可能会导致SIGABRT
,如果线程层导致不安全的行为。在使用parallel=True
运行 JIT 函数之前,可以首先指定安全的线程层。
通常,如果在使用 Numba 时遇到了段错误(SIGSEGV
),请将问题报告给Numba 问题跟踪器。
通过eval()
进行表达式评估
顶层函数pandas.eval()
实现了Series
和DataFrame
的高性能表达式评估。表达式评估允许将操作表示为字符串,并且可以通过一次评估大型DataFrame
上的所有算术和布尔表达式来提供性能改进。
注意
对于简单表达式或涉及小型 DataFrame 的表达式,不应使用eval()
。事实上,对于较小的表达式或对象,eval()
比纯 Python 慢几个数量级。一个好的经验法则是仅在具有超过 10,000 行的DataFrame
时使用eval()
。
支持的语法
这些操作由pandas.eval()
支持:
- 除了左移(
<<
)和右移(>>
)操作符之外的算术操作,例如,df + 2 * pi / s ** 4 % 42 - the_golden_ratio
- 包括链式比较在内的比较操作,例如,
2 < df < df2
- 布尔操作,例如,
df < df2 and df3 < df4 or not df_bool
list
和tuple
字面值,例如,[1, 2]
或(1, 2)
- 属性访问,例如,
df.a
- 下标表达式,例如,
df[0]
- 简单的变量评估,例如,
pd.eval("df")
(这并不是很有用) - 数学函数:
sin
、cos
、exp
、log
、expm1
、log1p
、sqrt
、sinh
、cosh
、tanh
、arcsin
、arccos
、arctan
、arccosh
、arcsinh
、arctanh
、abs
、arctan2
和log10
。
以下 Python 语法不允许:
- 表达式
- 除数学函数外的函数调用。
is
/is not
操作if
表达式lambda
表达式list
/set
/dict
推导式- 字面
dict
和set
表达式yield
表达式- 生成器表达式
- 由标量值组成的布尔表达式
- 语句
本地变量
您必须显式引用您想在表达式中使用的任何本地变量,方法是在名称前面放置@
字符。这个机制对于DataFrame.query()
和DataFrame.eval()
都是相同的。例如,
In [18]: df = pd.DataFrame(np.random.randn(5, 2), columns=list("ab")) In [19]: newcol = np.random.randn(len(df)) In [20]: df.eval("b + @newcol") Out[20]: 0 -0.206122 1 -1.029587 2 0.519726 3 -2.052589 4 1.453210 dtype: float64 In [21]: df.query("b < @newcol") Out[21]: a b 1 0.160268 -0.848896 3 0.333758 -1.180355 4 0.572182 0.439895
如果您没有使用@
前缀来引用本地变量,pandas 将引发异常,告诉您该变量未定义。
当使用DataFrame.eval()
和DataFrame.query()
时,这允许您在表达式中具有与局部变量和DataFrame
列相同的名称。
In [22]: a = np.random.randn() In [23]: df.query("@a < a") Out[23]: a b 0 0.473349 0.891236 1 0.160268 -0.848896 2 0.803311 1.662031 3 0.333758 -1.180355 4 0.572182 0.439895 In [24]: df.loc[a < df["a"]] # same as the previous expression Out[24]: a b 0 0.473349 0.891236 1 0.160268 -0.848896 2 0.803311 1.662031 3 0.333758 -1.180355 4 0.572182 0.439895
警告
pandas.eval()
如果您在该上下文中不能使用@
前缀,将会引发异常,因为它在那个上下文中未定义。
In [25]: a, b = 1, 2 In [26]: pd.eval("@a + b") Traceback (most recent call last): File ~/micromamba/envs/test/lib/python3.10/site-packages/IPython/core/interactiveshell.py:3577 in run_code exec(code_obj, self.user_global_ns, self.user_ns) Cell In[26], line 1 pd.eval("@a + b") File ~/work/pandas/pandas/pandas/core/computation/eval.py:325 in eval _check_for_locals(expr, level, parser) File ~/work/pandas/pandas/pandas/core/computation/eval.py:167 in _check_for_locals raise SyntaxError(msg) File <string> SyntaxError: The '@' prefix is not allowed in top-level eval calls. please refer to your variables by name without the '@' prefix.
在这种情况下,您应该像在标准 Python 中那样简单地引用变量。
In [27]: pd.eval("a + b") Out[27]: 3
pandas.eval()
解析器
有两种不同的表达语法解析器。
默认的'pandas'
解析器允许更直观的语法来表达类似查询的操作(比较、连接和或)。特别是,&
和|
运算符的优先级被设置为与相应的布尔操作and
和or
相等。
例如,上述连接可以不使用括号来编写。或者,您可以使用'python'
解析器来强制执行严格的 Python 语义。
In [28]: nrows, ncols = 20000, 100 In [29]: df1, df2, df3, df4 = [pd.DataFrame(np.random.randn(nrows, ncols)) for _ in range(4)] In [30]: expr = "(df1 > 0) & (df2 > 0) & (df3 > 0) & (df4 > 0)" In [31]: x = pd.eval(expr, parser="python") In [32]: expr_no_parens = "df1 > 0 & df2 > 0 & df3 > 0 & df4 > 0" In [33]: y = pd.eval(expr_no_parens, parser="pandas") In [34]: np.all(x == y) Out[34]: True
相同的表达式也可以用单词and
“与”在一起:
In [35]: expr = "(df1 > 0) & (df2 > 0) & (df3 > 0) & (df4 > 0)" In [36]: x = pd.eval(expr, parser="python") In [37]: expr_with_ands = "df1 > 0 and df2 > 0 and df3 > 0 and df4 > 0" In [38]: y = pd.eval(expr_with_ands, parser="pandas") In [39]: np.all(x == y) Out[39]: True
这里的and
和or
运算符具有与 Python 中相同的优先级。
pandas.eval()
引擎
有两种不同的表达式引擎。
'numexpr'
引擎是更高性能的引擎,可以相对于大型DataFrame
的标准 Python 语法带来性能改进。此引擎需要安装可选依赖项numexpr
。
'python'
引擎通常不有用,除非用于测试其他评估引擎。使用engine='python'
和可能会导致性能下降,不会获得任何性能优势。
In [40]: %timeit df1 + df2 + df3 + df4 7.42 ms +- 81.8 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
In [41]: %timeit pd.eval("df1 + df2 + df3 + df4", engine="python") 8.11 ms +- 161 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
DataFrame.eval()
方法
除了顶层pandas.eval()
函数外,您还可以在DataFrame
的“上下文”中评估表达式。
In [42]: df = pd.DataFrame(np.random.randn(5, 2), columns=["a", "b"]) In [43]: df.eval("a + b") Out[43]: 0 -0.161099 1 0.805452 2 0.747447 3 1.189042 4 -2.057490 dtype: float64
任何有效的pandas.eval()
表达式也是有效的DataFrame.eval()
表达式,额外的好处是您不必在要评估的列名前加上DataFrame
的名称。
此外,您可以在表达式中执行列的赋值。这允许公式化评估。赋值目标可以是新列名或现有列名,并且必须是有效的 Python 标识符。
In [44]: df = pd.DataFrame(dict(a=range(5), b=range(5, 10))) In [45]: df = df.eval("c = a + b") In [46]: df = df.eval("d = a + b + c") In [47]: df = df.eval("a = 1") In [48]: df Out[48]: a b c d 0 1 5 5 10 1 1 6 7 14 2 1 7 9 18 3 1 8 11 22 4 1 9 13 26
返回具有新列或修改列的DataFrame
的副本,原始框架保持不变。
In [49]: df Out[49]: a b c d 0 1 5 5 10 1 1 6 7 14 2 1 7 9 18 3 1 8 11 22 4 1 9 13 26 In [50]: df.eval("e = a - c") Out[50]: a b c d e 0 1 5 5 10 -4 1 1 6 7 14 -6 2 1 7 9 18 -8 3 1 8 11 22 -10 4 1 9 13 26 -12 In [51]: df Out[51]: a b c d 0 1 5 5 10 1 1 6 7 14 2 1 7 9 18 3 1 8 11 22 4 1 9 13 26
可以通过使用多行字符串执行多列赋值。
In [52]: df.eval( ....: """ ....: c = a + b ....: d = a + b + c ....: a = 1""", ....: ) ....: Out[52]: a b c d 0 1 5 6 12 1 1 6 7 14 2 1 7 8 16 3 1 8 9 18 4 1 9 10 20
标准 Python 中的等效操作将是
In [53]: df = pd.DataFrame(dict(a=range(5), b=range(5, 10))) In [54]: df["c"] = df["a"] + df["b"] In [55]: df["d"] = df["a"] + df["b"] + df["c"] In [56]: df["a"] = 1 In [57]: df Out[57]: a b c d 0 1 5 5 10 1 1 6 7 14 2 1 7 9 18 3 1 8 11 22 4 1 9 13 26
Pandas 2.2 中文官方教程和指南(二十三)(4)https://developer.aliyun.com/article/1508856