T
traeai
登录
返回首页
KDnuggets

数据科学家必须掌握的5个Python概念

8.2Score
数据科学家必须掌握的5个Python概念

TL;DR · AI 摘要

本文介绍了数据科学家必须掌握的5个Python核心概念,重点强调了NumPy向量化和广播机制在提升数据处理性能中的关键作用,通过对比传统循环与向量化实现,展示了性能提升可达26倍以上。

核心要点

  • 使用NumPy向量化可将数组运算速度提升至传统Python循环的26倍以上
  • NumPy广播机制允许不同形状数组进行元素级运算而无需复制数据
  • 数据科学中应避免使用标准Python循环处理大规模数据集

结构提纲

按章节快速跳转。

  1. 数据科学家不应仅因流行而使用Python,需掌握高性能编程技巧以克服其动态类型和解释执行的性能瓶颈。

  2. ·NumPy向量化

    通过将循环操作移至预编译C扩展中,NumPy可实现数组的批量处理,显著提升计算效率。

  3. NumPy广播规则允许不同形状的数组进行元素级运算,通过虚拟扩展避免内存复制。

  4. 在处理1000万数据点时,NumPy向量化比Python循环快26倍,证明了向量化的重要性。

  5. 数据预处理如去均值、归一化等操作可通过广播机制高效实现,无需显式循环。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 5 Must-Know Python Concepts for Data Scientists
    • NumPy Vectorization
      • Avoid Python loops
      • Use pre-compiled C extensions
      • Achieve 26x speedup
    • Broadcasting
      • Handle mismatched dimensions
      • No data duplication
      • Element-wise operations
    • Performance Optimization
      • Replace procedural code
      • Use vectorized approaches
      • Memory-aware programming

金句 / Highlights

值得收藏与分享的关键句。

  • 标准Python循环很慢,因为每次迭代都会产生大量开销:类型检查、动态方法查找和引用计数。

    第1节

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 通过向量化算术运算,我们可以用更简洁、更清晰的代码实现巨大的性能提升。

    第1节

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 广播允许对不同形状的数组进行元素级运算,通过在缺失或单元素维度上虚拟扩展较小的数组,而无需复制任何内存数据。

    第2节

    ⬇︎ 下载 PNG𝕏 分享到 X
#Python#数据科学#NumPy#向量化#性能
打开原文

数据科学家必须掌握的 5 个 Python 概念 - KDnuggets

URL 来源: https://www.kdnuggets.com/5-must-know-python-concepts-for-data-scientists

发布日期: 2026 年 6 月 2 日 星期二 06:08:31 GMT

图 1:数据科学家必须掌握的 5 个 Python 概念

#引言

你不应该仅仅因为“别人都在用”就使用 Python 进行数据分析!Python 在数据领域的主导地位并非偶然。它是一种基于高度表达性、可读性强的语法构建的语言,抽象掉了底层的内存管理。然而,这种高级抽象也带来了代价:标准 Python 执行是动态类型和解释型的,这可能导致原始迭代过程变得非常缓慢。

为了编写高性能的数据系统,数据科学家必须从标准的过程式编码模式转向专门的、向量化和内存感知的方法。在本文中,我们将深入探讨五个必须了解的 Python 概念,帮助你从编写笨拙、缓慢的意大利面代码,转变为构建闪电般快速、生产级且功能优美的数据管道。

#1. NumPy 向量化

标准 Python 循环速度很慢。由于 Python 是一种解释型语言,每次 for 循环的迭代都会产生显著的开销:类型检查、动态方法查找和引用计数。当你处理数百万个数据点时,这些微小的开销会累积成几秒的瓶颈。

解决方案是 NumPy 的向量化。与在 Python 字节码中顺序处理元素不同,NumPy 将循环卸载到高度优化的预编译 C 扩展中。这些操作一次作用于整个数组,以机器级别执行连续的数组块,通常利用单指令多数据(SIMD)指令。

#### //笨拙的方式

假设我们有一个包含一百万个浮点值的列表,代表原始传感器读数,我们需要将每个读数乘以 1.5,并应用一个校准常数 10.0。使用显式的 Python 循环:

code
import time

# 一个包含 1000 万个传感器读数的大列表
n_elements = 10_000_000
data_list = [float(x) for x in range(n_elements)]

# 使用显式 Python 循环缩放值
start_time = time.time()
scaled_list = []

for val in data_list:
    scaled_list.append(val * 1.5 + 10.0)

loop_duration = time.time() - start_time

print(f"循环实现耗时: {loop_duration:.6f} 秒")

输出:

循环实现耗时: 0.378866 秒

#### //向量化的方式

这是优雅的向量化替代方案。我们将数据加载到连续的 NumPy 数组中,并直接对数组对象执行算术运算:

code
import numpy as np
import time

# 一个包含 1000 万个传感器读数的大列表
n_elements = 10_000_000

# 向量化方式:NumPy 在预编译的 C 循环中执行整个计算
data_array = np.arange(n_elements, dtype=float)

start_time = time.time()
scaled_array = data_array * 1.5 + 10.0
numpy_duration = time.time() - start_time

print(f"NumPy 实现耗时: {numpy_duration:.6f} 秒")
print(f"提速: {loop_duration / numpy_duration:.1f} 倍更快!")

输出:

code
循环实现耗时: 0.348456 秒
NumPy 实现耗时: 0.013395 秒
提速: 26.0 倍更快!

通过向量化算术运算,我们可以实现巨大的性能提升,同时代码更清晰、更简洁。循环从 Python 空间中移除,完全在高速 C 空间中执行。

#2. 广播:不匹配维度的数学规则

在线性代数中,矩阵运算通常要求两个操作数具有完全相同的形状。然而,在数据科学中,我们经常需要对不同维度的数组执行操作,例如从数据集中减去特征列的平均值,或归一化行值。

为了避免复制数据以强制匹配形状,NumPy 使用一组称为 广播 的数学规则。广播允许在不同形状的数组上进行逐元素操作,通过虚拟地沿缺失或单元素维度扩展较小的数组,而无需在内存中复制任何数据。

广播规则如下:

  1. 如果数组的秩(维度数量)不相同,则在较低秩数组的形状前添加 1s,直到两个形状长度相同
  2. 两个维度兼容,如果它们相等,或者其中一个为 1
  3. 如果兼容,数组的行为就像沿着大小为 1 的维度拉伸以匹配另一个数组的形状一样

#### //笨拙的方式

假设我们有一个 3x4 特征矩阵(3 个样本,4 个特征),并希望减去列均值以“去均值”特征:

code
import numpy as np

features = np.array([
    [10.0, 20.0, 30.0, 4.0],
    [12.0, 24.0, 36.0, 8.0],
    [14.0, 28.0, 42.0, 12.0]
])

# 每个特征列的均值(形状:(4,))
col_means = np.mean(features, axis=0)

# 使用嵌套循环手动去均值
demeaned_clunky = np.zeros_like(features)
for idx in range(features.shape[0]):
    for col_idx in range(features.shape[1]):
        demeaned_clunky[idx, col_idx] = features[idx, col_idx] - col_means[col_idx]

# 替代方案:平铺数组以强制匹配形状
tiled_means = np.tile(col_means, (features.shape[0], 1))
demeaned_tiled = features - tiled_means

#### //Python 风格的方式

通过广播,我们可以直接执行减法。NumPy 通过将列均值形状视为 (1, 4),自动将 (3, 4) 特征矩阵与 (4,) 列均值数组对齐:

code
import numpy as np

features = np.array([
    [10.0, 20.0, 30.0, 4.0],
    [12.0, 24.0, 36.0, 8.0],
    [14.0, 28.0, 42.0, 12.0]
])

col_means = np.mean(features, axis=0)

# 通过自动广播进行 Python 风格的减法
demeaned_broadcasting = features - col_means

按行求和进行逐行归一化

row_sums 的形状为 (3,) -> 为了将 (3, 4) 除以 (3,),我们使用 np.newaxis 扩展形状为 (3, 1)

row_sums = np.sum(features, axis=1) normalized_features = features / row_sums[:, np.newaxis]

print("去均值:\n", demeaned_broadcasting) print("\n归一化行:\n", normalized_features)

code

输出:

去均值: [[-2. -4. -6. -4.] [ 0. 0. 0. 0.] [ 2. 4. 6. 4.]]

归一化行: [[0.15625 0.3125 0.46875 0.0625 ] [0.15 0.3 0.45 0.1 ] [0.14583333 0.29166667 0.4375 0.125 ]]

code

广播机制消除了重复值和内存复制。在底层,NumPy 以 C 语言的速度运行减法循环,而无需创建临时的 tiled 矩阵,从而保留了内存带宽并加速了操作。

## #3. Pandas 的 .pipe() 和 .assign() 方法:简洁、功能化的数据管道

在 [Pandas](https://pandas.pydata.org/) 中,数据准备通常会退化为顺序的混乱代码。开发者会创建多个中间 DataFrame(如 df1、df2 等),就地修改变量,或链式调用方括号。这会导致代码难以阅读、难以测试,并且极易出现令人头疼的 `SettingWithCopyWarning` 警告。

现代 Pandas 鼓励从过程式修改转向功能化、声明式的数据管道。通过利用 [`.assign()`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.assign.html) 进行特征创建和 [`.pipe()`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.pipe.html) 进行可重用的多列操作,你可以在单个管道中链接步骤。

#### // 传统方式

让我们处理一个原始的客户销售数据集,需要过滤异常值、标准化字符串、填充缺失值并计算销售税。

import pandas as pd import numpy as np

raw_data = { 'Customer_ID': [101, 102, 103, 104, 105], 'Age': [25, -5, 47, 120, 31], 'Country': ['usa', 'CANADA', 'usa', 'Germany', 'canada'], 'Raw_Spend': [120.50, 450.00, 80.00, np.nan, 300.00] } df = pd.DataFrame(raw_data)

顺序的中间变异

df_clean = df.copy()

1. 过滤无效年龄

df_clean = df_clean[(df_clean['Age'] >= 0) & (df_clean['Age'] <= 100)]

2. 标准化国家名称(可能导致复制警告)

df_clean['Country'] = df_clean['Country'].str.upper().str.strip()

3. 填充缺失的 Raw_Spend 值

median_spend = df_clean['Raw_Spend'].median() df_clean['Raw_Spend'] = df_clean['Raw_Spend'].fillna(median_spend)

4. 计算 Taxed_Spend

df_clean['Taxed_Spend'] = df_clean['Raw_Spend'] * 1.15

5. 格式化列名

df_clean = df_clean.rename(columns={'Customer_ID': 'customer_id'})

code

#### // Pythonic 方式

将此问题视为一个功能方法链问题,我们可以将国家标准化步骤封装成一个可重用的实用函数,并构建一个单一、简洁、自包含的管道。

import pandas as pd import numpy as np

raw_data = { 'Customer_ID': [101, 102, 103, 104, 105], 'Age': [25, -5, 47, 120, 31], 'Country': ['usa', 'CANADA', 'usa', 'Germany', 'canada'], 'Raw_Spend': [120.50, 450.00, 80.00, np.nan, 300.00] } df = pd.DataFrame(raw_data)

用于 .pipe() 的可重用自定义转换函数

def standardize_countries(dataframe: pd.DataFrame) -> pd.DataFrame: df_out = dataframe.copy() df_out['Country'] = df_out['Country'].str.upper().str.strip() return df_out

单一优雅的功能管道

df_clean_pipeline = ( df.query("Age >= 0 and Age <= 100") .assign( Raw_Spend=lambda x: x['Raw_Spend'].fillna(x['Raw_Spend'].median()), Taxed_Spend=lambda x: x['Raw_Spend'] * 1.15 ) .pipe(standardize_countries) .rename(columns={'Customer_ID': 'customer_id'}) )

print(df_clean_pipeline)

code

输出:

customer_id Age Country Raw_Spend Taxed_Spend 0 101 25 USA 120.5 138.5750 2 103 47 USA 80.0 92.0000 4 105 31 CANADA 300.0 345.0000

code

方法链确保你的原始 DataFrame 的状态永远不会被意外修改,防止副作用。`.assign()` 通过接收一个 lambda 函数来处理列赋值,其中 `x` 指代链中该点的 DataFrame 当前状态,而 `.pipe()` 允许自定义操作被干净地模块化。

## #4. 使用 Lambda 函数进行数据转换

特征工程经常需要小型的、单一用途的转换,例如格式化字符串、拆分值或应用条件语句。为这些简单计算编写自定义命名函数(使用 `def`)会给脚本增加不必要的样板代码。

更优雅的方法是在 Pandas 的 [`.map()`](https://pandas.pydata.org/docs/reference/api/pandas.Series.map.html) 和 [`.apply()`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.apply.html) 中使用 [lambda 函数](https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions)。Lambda 函数是匿名的、一次性使用的函数,可以即时定义而无需命名,非常适合快速数据映射和简洁的内联转换。

#### // 传统方式

假设我们有一个员工数据集,需要映射他们的远程工作状态并解析他们的姓氏。常见的错误是编写手动循环或使用 `iterrows()`:

import pandas as pd

df = pd.DataFrame({ 'employee_name': ['john doe', 'jane smith', 'bob johnson'], 'department_code': ['IT_01', 'HR_02', 'IT_03'], 'is_remote': [1, 0, 1] })

逐行迭代(缓慢且管理繁琐)

df_clunky = df.copy() df_clunky['remote_status'] = None df_clunky['last_name'] = None

code

for index, row in df_clunky.iterrows():

解析远程状态

if row['is_remote'] == 1: df_clunky.at[index, 'remote_status'] = "Remote" else: df_clunky.at[index, 'remote_status'] = "Office"

解析并大写姓氏

name_parts = row['employee_name'].split() df_clunky.at[index, 'last_name'] = name_parts[1].capitalize()

#### // Pythonic 方式

以下是使用内联 lambda 转换的简洁、声明式方法。我们通过 .map() 进行简单的转换,通过 .apply() 进行自定义字符串操作,即时转换列:

code
import pandas as pd

df = pd.DataFrame({
    'employee_name': ['john doe', 'jane smith', 'bob johnson'],
    'department_code': ['IT_01', 'HR_02', 'IT_03'],
    'is_remote': [1, 0, 1]
})

# 嵌套在 map() 和 apply() 中的 lambdas
df_opt = df.assign(
    remote_status=lambda d: d['is_remote'].map(lambda val: "Remote" if val == 1 else "Office"),
    last_name=lambda d: d['employee_name'].apply(lambda name: name.split()[-1].capitalize()),
    dept_level=lambda d: d['department_code'].apply(lambda code: code.split('_')[-1])
)

print(df_opt[['employee_name', 'last_name', 'remote_status', 'dept_level']])

输出:

code
employee_name last_name remote_status dept_level
0      john doe       Doe        Remote         01
1    jane smith     Smith        Office         02
2   bob johnson   Johnson        Remote         03

使用 lambda 允许你编写自包含的转换逻辑,使你的逻辑紧密绑定到列创建语句中。通过将 lambda 与 .map().apply() 结合使用,你可以消除冗长的嵌套循环,并保持代码的可读性。

#5. 数据框的内存管理:优化数据类型

默认情况下,当 Pandas 导入数据集(例如来自 CSV 或数据库文件)时,它会采取保守策略。整数被加载为 64 位(int64),小数为 64 位(float64),文本列则为通用的 object 类型。虽然安全,但这种默认设置会导致最大的内存占用。一个只有几十万行的数据集可以迅速消耗数 GB 的系统 RAM,导致本地运行变慢或在生产服务器上出现“内存不足”错误。

我们可以通过将数值列向下转换为较小的整数/浮点数,并将低基数文本列转换为 category 数据类型,大幅减少 DataFrame 的内存占用。

例如,年龄列的值范围从 0 到 100,可以轻松放入单个 8 位整数(int8,可容纳最多 127)而不是标准的 64 位(int64)数据类型。同样,类别值在底层将文本字符串映射为简单的整数编码,从而实现巨大的空间节省。

#### // 冗长的方式

让我们生成一个 10 万用户的合成订阅者数据集,并查看默认 Pandas 类型所消耗的内存:

code
import pandas as pd
import numpy as np

n_rows = 100_000
np.random.seed(42)

df_large = pd.DataFrame({
    'user_id': np.random.randint(1000000, 1000000 + n_rows, size=n_rows),
    'age': np.random.randint(18, 90, size=n_rows),
    'device_type': np.random.choice(['iOS', 'Android', 'Web', 'SmartTV'], size=n_rows),
    'monthly_revenue': np.random.uniform(5.0, 150.0, size=n_rows),
    'active_subscriber': np.random.choice([0, 1], size=n_rows)
})

# 检查内存使用情况
print(df_large.info(memory_usage='deep'))
memory_before = df_large.memory_usage(deep=True).sum() / (1024 ** 2)
print(f"默认内存使用量: {memory_before:.2f} MB")

输出:

code
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 5 columns):
 #   Column             Non-Null Count   Dtype  
---  ------             --------------   -----  
 0   user_id            100000 non-null  int64  
 1   age                100000 non-null  int64  
 2   device_type        100000 non-null  object 
 3   monthly_revenue    100000 non-null  float64
 4   active_subscriber  100000 non-null  int64  
dtypes: float64(1), int64(3), object(1)
memory usage: 8.2 MB
None
默认内存使用量: 8.20 MB

#### // Pythonic 方式

现在让我们应用我们的优化:将列转换为其所需的最小数值边界,并将文本列转换为 category

code
# 向下转换类型
df_optimized = df_large.assign(
    user_id=df_large['user_id'].astype('int32'),                    # 最大 110 万适合 int32
    age=df_large['age'].astype('int8'),                             # 最大年龄 90 适合 int8
    device_type=df_large['device_type'].astype('category'),         # 低基数(4 个唯一字符串)
    monthly_revenue=df_large['monthly_revenue'].astype('float32'),  # 单精度浮点数足够
    active_subscriber=df_large['active_subscriber'].astype('int8')  # 二进制标志适合 int8
)

# 检查优化后的内存使用情况
print(df_optimized.info(memory_usage='deep'))
memory_after = df_optimized.memory_usage(deep=True).sum() / (1024 ** 2)

print(f"优化后内存使用量: {memory_after:.2f} MB")
print(f"内存足迹减少: {((memory_before - memory_after) / memory_before) * 100:.1f}%")

输出:

code
memory usage: 1.0 MB
None
优化后内存使用量: 1.05 MB
内存足迹减少: 87.2%

通过简单地调整我们的列 dtypes,我们将 DataFrame 的大小缩小了 近 90%!通过为低基数字符串使用 category,Pandas 避免了在行之间重复字符字符串,而是将每行映射到一个轻量级整数索引。

# 总结

掌握这五个基本的 Python 概念是迈向高级数据科学家的重要一步,能够设计出高效、可读且高度优化的数据管道。

code

通过利用 NumPy 中的向量化和广播机制,您可以消除原始的 Python 循环,从而实现硬件级别的速度提升。使用 `.pipe()` 和 `.assign()` 将功能型 Pandas 流水线化,可以显著提高特征工程工作流的可读性和安全性。结合内联 `lambda` 函数进行即时转换,并通过 `dtypes` 实现主动内存管理,使您的算法能够无缝地从本地原型扩展到大规模生产负载。

数据科学与软件工程一样,也是一门数学学科。将您的代码视为第一优先级的产品,这样您的数据集处理会更快,您的流水线故障更少,您的系统也会成为一种构建的乐趣。

请务必查看本系列之前的文章:

*   [5 个必须知道的 Python 概念](https://www.kdnuggets.com/5-must-know-python-concepts)
*   [5 个更多必须知道的 Python 概念](https://www.kdnuggets.com/5-more-must-know-python-concepts)

[](https://www.linkedin.com/in/mattmayo13/)****[Matthew Mayo](https://www.kdnuggets.com/wp-content/uploads/profile-pic.jpg)**** ([**@mattmayo13**](https://twitter.com/mattmayo13)) 拥有计算机科学硕士学位和数据挖掘研究生文凭。作为 [KDnuggets](https://www.kdnuggets.com/) 和 [Statology](https://www.statology.org/) 的主编,以及 [Machine Learning Mastery](https://machinelearningmastery.com/) 的特约编辑,Matthew 致力于让复杂的数据科学概念变得易于理解。他的专业兴趣包括自然语言处理、语言模型、机器学习算法以及探索新兴的人工智能。他致力于推动数据科学社区的知识民主化。Matthew 从6岁起就开始编程。

AI 可能会生成不准确的信息,请核实重要内容

数据科学家必须掌握的5个Python概念 | KDnuggets | traeai