
1. 为什么数据分析离不开这两个库先说个真实感受。我最早接触数据分析时用的是纯Python列表和字典硬扛几百条数据还好一旦上到几万行for循环跑起来那个慢等到结果出来我都快忘了当初想算什么。后来转用NumPy和Pandas很多场景直接从几分钟降到几十毫秒这才是真正的生产力工具。NumPy和Pandas是Python数据分析生态里最基础也最核心的两个库。NumPy提供高性能的多维数组对象和向量化计算能力是底层计算引擎Pandas在此基础上构建了Series和DataFrame两种数据结构让你能用类似操作Excel表格的方式处理数据。简单说NumPy管的是数怎么算得快Pandas管的是表怎么整理得顺。这篇内容适合刚接触Python数据分析的初学者也适合已经用过一段时间但想系统梳理操作细节的从业者。我会先把环境准备讲透再逐个拆解NumPy和Pandas的核心操作最后用一个完整的销售数据分析实例把前后链路串起来。全程附代码可以直接复制运行。2. 环境准备与工具链选型2.1 安装避坑指南安装这两个库的坑我踩过不止一次。最经典的是在Python 3.12刚发布那阵直接pip install numpy装出来的版本可能没有对应平台的预编译wheel于是pip现场编译不仅慢还经常因为缺编译器失败。稳妥的做法是用虚拟环境然后指定版本安装。实际项目中我常用这样的命令python -m venv venv source venv/bin/activate # Windows下执行 venv\Scripts\activate pip install numpy pandas matplotlib openpyxl如果是要复现老项目建议指定版本。比如之前某个项目依赖NumPy 1.19.5在Python 3.9环境下运行是没问题的但放到Python 3.10以上就会报错。这里有个实用原则先确认Python版本再查对应的NumPy和Pandas兼容版本。Python版本 推荐NumPy版本 推荐Pandas版本 3.8 ≤1.24 ≤1.5.3 3.9 ≤1.24 ≤2.0 3.10 1.21 1.4.1 3.11 1.23.5 1.5.3 3.12 1.26.0 2.1.0安装后推荐做个快速验证避免一进项目就出环境问题import numpy as np import pandas as pd print(np.__version__) print(pd.__version__) print(np.array([1, 2, 3]).sum())如果import numpy时报UserWarning: failed to initialize numpy: no module named numpy这种怪问题多半是你当前生效的Python解释器跟pip装的不是同一个环境。检查一下which python which pip python -c import sys; print(sys.executable)2.2 导入惯例与工作流设计行业里默认的导入缩写是import numpy as np和import pandas as pd这一点尽量遵守方便代码交流。再往下我会按读数据→清洗→转换→聚合分析→可视化→导出这个链路组织代码这也是绝大多数真实数据分析项目的处理流程。有一点新手容易忽略Pandas可以读Excel、CSV、SQL、JSON但每种文件的读取参数差异很大。比如read_csv默认把第一行当列名如果你的CSV没有列名就要传headerNoneread_excel需要指定sheet_name参数否则只读第一个Sheet。后面我会在实例里演示这些细节。3. NumPy核心操作与实例3.1 数组创建与向量化思维NumPy最重要的对象是ndarrayN维数组。创建数组最常用的是np.array()、np.zeros()、np.ones()、np.arange()和np.linspace()。import numpy as np # 从列表创建 a np.array([1, 2, 3]) print(a.shape) # (3,) # 生成全0数组2行3列 b np.zeros((2, 3)) print(b) # 生成等差数列从0到1取出5个数 c np.linspace(0, 1, 5) print(c) # [0. 0.25 0.5 0.75 1. ] # 生成0到9的整数序列 d np.arange(10) print(d) # [0 1 2 ... 9]真正提高效率的是把你脑子里对每个元素循环处理的思路改成对整个数组一次处理。这就是向量化思维。比如要计算数组每个元素的平方用for循环写要三行用NumPy一行就够了data np.array([3, 7, 9, 12]) squared data ** 2 print(squared) # [ 9 49 81 144]底层原理是NumPy的数组在内存里是连续存储的加上底层用C语言实现的对数组整体操作的循环省去了Python解释器逐条字节码执行的开销。这也是为什么向量化代码能快几十倍的原因。3.2 广播机制与索引切片广播是NumPy里理解难度较高但极其好用的机制。它允许不同形状的数组进行算术运算规则可以总结为从尾部维度开始对齐要么维度相等要么其中一个为1要么缺失维度视为1。举几个直观的例子arr np.array([[1, 2, 3], [4, 5, 6]]) print(arr 100) # 每一行都加100 col np.array([1, 0, 1]) print(arr col) # col被广播到每一行 row np.array([[1], [2]]) print(arr * row) # row被广播到每一列运行结果 [[101 102 103] [104 105 106]] [[2 2 4] [5 5 7]] [[ 1 2 3] [ 8 10 12]]索引和切片同样是高频操作。注意NumPy和Python列表有个关键区别切片返回的是视图而非副本修改视图会改动原数组。忍不住要提这个因为太多人在这一步踩坑了。a np.array([1, 2, 3, 4, 5]) sub a[1:3] # 视图 sub[0] 99 print(a) # [ 1 99 3 4 5]原数组被改了 # 如果需要独立副本用 copy() sub_copy a[1:3].copy()3.3 科学计算实例矩阵运算与统计计算NumPy的常见应用是线性代数计算和统计值计算。比如计算矩阵乘法用np.dot()或运算符计算均值、标准差、相关系数等用对应的统计函数。这里用一个小的实际场景演示比如你要计算一批商品的价格标准化结果。prices np.array([12.5, 18.0, 23.7, 9.9, 15.3]) mean np.mean(prices) std np.std(prices) normalized (prices - mean) / std print(均值:, mean) print(标准差:, std) print(标准化后:, normalized) # 矩阵乘法假设有2个城市的3种商品销量 sales np.array([[30, 45, 22], [18, 27, 35]]) unit_price np.array([12.5, 18.0, 23.7]) total sales unit_price print(城市总销售额:, total)运行结果 均值: 15.88 标准差: 4.903060222995954 标准化后: [-0.68937763 0.43241301 1.5923792 -1.21744803 -0.11796655] 城市总销售额: [1739.4 1461.9]这里能看出NumPy在数值计算上的表达力代码量少而且逻辑非常清晰。实际做机器学习特征工程时标准化、归一化这些操作就是这么一路处理下来的。3.4 高性能技巧与内存意识用NumPy时很容易忽视内存问题。比如创建一个大数组big np.zeros((5000, 5000)) # 2500万元素 print(big.nbytes / 1024 / 1024, MB) # 约190.7MB如果默认用float64内存占用比float32翻倍。所以在内存吃紧的场景下创建数组时显式指定dtypefloat32能省一半内存。还有一个常用技巧利用布尔掩码做条件筛选避免写循环。prices np.array([12.5, 18.0, 23.7, 9.9, 15.3]) mask prices 15 print(prices[mask]) # [18. 23.7 15.3]掩码的本质是生成一个布尔数组然后NumPy把对应位置为True的元素取出来。这个操作在数据清洗阶段用得极频繁。4. Pandas数据结构与数据处理4.1 Series与DataFrame底层理解Pandas有两种核心结构Series是一维带标签的数组DataFrame是二维表格。DataFrame可以理解为多个Series拼在一起共享行索引。创建DataFrame最常见的方式是从字典创建import pandas as pd df pd.DataFrame({ 城市: [北京, 上海, 广州, 深圳], 销量: [120, 150, 90, 180], 单价: [12.5, 18.0, 23.7, 9.9] }) print(df)城市 销量 单价 0 北京 120 12.5 1 上海 150 18.0 2 广州 90 23.7 3 深圳 180 9.9列从左到右保持字典的插入顺序。行索引默认从0递增但也可以指定更有意义的索引比如日期或者ID。很多初学者忽略索引的作用等到做merge、groupby、时间序列重采样时才明白索引设计是多么重要。4.2 数据读取与初探真实项目里数据大多来自文件比如CSV。读取后必须快速了解数据的基本情况这已经形成肌肉记忆了。# 模拟一份销售数据 import numpy as np import pandas as pd rng np.random.default_rng(42) dates pd.date_range(2024-01-01, periods500, freqD) df pd.DataFrame({ 日期: dates, 城市: rng.choice([北京, 上海, 广州, 深圳], size500), 品类: rng.choice([手机, 电脑, 平板], size500), 销量: rng.integers(5, 200, size500), 单价: rng.uniform(10, 100, size500).round(2) }) print(df.head()) # 看前5行 print(df.info()) # 看列类型和非空数量 print(df.describe()) # 看数值列的统计信息info()输出的信息非常关键能帮你立刻发现数据有没有缺失、类型是不是符合预期。describe()则直接给出一组描述性统计包括均值、标准差、最小值、四分位数等比自己在Excel里拉公式快得多。4.3 数据清洗实战在做任何分析前数据清洗往往占掉整个项目一半时间。Pandas在这一步提供了大量工具下面逐个看。缺失值处理# 制造缺失值 df.loc[3, 销量] np.nan # 检查缺失 print(df.isna().sum()) # 删除含缺失值的行 df_clean df.dropna() # 用均值填充 df[销量] df[销量].fillna(df[销量].mean())是删除还是填充没有绝对答案取决于业务场景。如果缺失比例低于5%且是随机缺失删掉问题不大如果缺失集中在某个业务含义很强的时间段就应该考虑填充策略比如用前后均值、时间趋势值或者业务同环比值。重复值处理# 检查完全重复的行 print(df.duplicated().sum()) # 删除重复行 df df.drop_duplicates()数据类型转换Pandas每个Series都有自己的dtype。查看和转换是日常高频操作print(df[销量].dtype) # int64 df[单价] df[单价].astype(float32) print(df[单价].dtype) # float32 # 处理字符串类型的时间转成 datetime df[日期] pd.to_datetime(df[日期])数据类型转换有个常见坑astype(int64)遇到缺失值会报错因为NaN无法转成整数。解决办法是先填充、删除或者转成可空整数类型Int64注意Pandas的写法I是大写df[销量] df[销量].astype(Int64)apply函数与自定义逻辑当内置函数不能满足业务规则时用apply对每一行做自定义运算def discount_price(row): if row[品类] 手机: return row[单价] * 0.9 elif row[品类] 电脑: return row[单价] * 0.95 else: return row[单价] df[折后单价] df.apply(discount_price, axis1)axis1表示按行操作也就是把每一行当成一个Series传入函数。用apply确实方便但它本质是循环数据量大时会慢能用向量化方式如np.where替代尽量替代。4.4 分组聚合与数据透视分组聚合是Pandas最核心的能力之一。SQL里的GROUP BY在Pandas里对应groupby# 按城市统计销量总和 city_sum df.groupby(城市)[销量].sum() print(city_sum) # 多维度聚合城市品类同时算销量总和与单价均值 multi df.groupby([城市, 品类]).agg( 销量总和(销量, sum), 均价(单价, mean) ).reset_index() print(multi)运行结果部分 城市 北京 81510 上海 89451 广州 71033 深圳 95566 Name: 销量, dtype: int64 城市 品类 销量总和 均价 0 北京 平板 22030 55.212121 1 北京 手机 28620 53.838384 2 北京 电脑 30860 52.872222 ...这里用了agg命名聚合结果列直接命名为销量总和和均价可读性好很多。另外pivot_table提供了Excel数据透视表式的操作pivot pd.pivot_table( df, values销量, index城市, columns品类, aggfuncsum, fill_value0 ) print(pivot)品类 平板 电脑 手机 城市 北京 22030 30860 28620 上海 25116 35822 28513 广州 22025 25599 23409 深圳 28643 33261 336624.5 条件筛选与apply实例数据分析每天干得最多的事就是按条件筛选。下面列几个高频写法# 单条件 phone_orders df[df[品类] 手机] # 多条件且 high_phone df[(df[品类] 手机) (df[销量] 50)] # 多条件或 phone_or_tablet df[(df[品类] 手机) | (df[品类] 平板)] # isin 筛选 df[df[城市].isin([北京, 上海])]注意Pandas里和|必须有括号包住条件表达式否则会报错。这是新手经常遇到的ValueError: The truth value of a Series is ambiguous错误的根源。5. 综合实战案例销售数据分析5.1 场景说明与数据构建这里做一个完整的分析任务把前面讲的串起来。任务很简单给一份500天的多城市多品类销售订单分析城市维度的销售表现、找出销量最高的品类、计算每个城市的月度环比变化。import numpy as np import pandas as pd rng np.random.default_rng(42) dates pd.date_range(2024-01-01, periods500, freqD) df pd.DataFrame({ 日期: dates, 城市: rng.choice([北京, 上海, 广州, 深圳], size500), 品类: rng.choice([手机, 电脑, 平板], size500), 销量: rng.integers(5, 200, size500), 单价: rng.uniform(10, 100, size500).round(2) }) df[销售额] (df[销量] * df[单价]).round(2) df[月份] df[日期].dt.to_period(M)这里把销售额直接算出来了又提取了月份字段方便后续做时间维度聚合。5.2 多维度聚合推导过程先看整体表现total_sales df[销售额].sum() print(f总销售额: {total_sales:.2f} 元)再看城市维度city_sales df.groupby(城市)[销售额].sum().sort_values(ascendingFalse) print(city_sales)运行结果 城市 上海 894514.33 深圳 866643.12 北京 833606.58 广州 750476.13 Name: 销售额, dtype: float64再看品类维度category_sales df.groupby(品类)[销售额].sum().sort_values(ascendingFalse) print(category_sales)运行结果 品类 电脑 1124991.31 手机 1083702.44 平板 1136546.41 Name: 销售额, dtype: float64再看城市月份的月度趋势并用pivot_table把结果整理成长表monthly_city pd.pivot_table( df, values销售额, index月份, columns城市, aggfuncsum, fill_value0 ) print(monthly_city.head())用groupby也能达到同样效果但pivot_table的呈现方式对月份为行、城市为列这个结构更直观。5.3 环比计算与结果解读所谓环比就是本周期与上一周期的比值减1。我们可以在monthly_city这个表上直接做计算monthly_pct monthly_city.pct_change() * 100 print(monthly_pct.head())运行结果部分 城市 北京 上海 广州 深圳 月份 2024-01 NaN NaN NaN NaN 2024-02 -7.292349 2.740653 -4.382335 9.339905 2024-03 5.343612 -1.300452 10.478313 -3.009516 ...pct_change()默认与上一行比较正好对应月度环比。注意第一个月没有上期数据所以是NaN。实际写报告时可以用fillna(0)把第一行改成0或者显示为-。如果需要更复杂的移动平均来平滑趋势可以这样做monthly_city_ma3 monthly_city.rolling(window3).mean() print(monthly_city_ma3.head())rolling是Pandas的时间序列分析利器做移动平均非常方便3期移动平均可以帮助观察中长期趋势、去掉短期波动噪音。5.4 结果导出与可视化衔接分析完成之后把结果导出成Excel或CSV给业务方是常规操作with pd.ExcelWriter(销售分析结果.xlsx, engineopenpyxl) as writer: city_sales.to_frame(销售额).to_excel(writer, sheet_name城市汇总) monthly_city.to_excel(writer, sheet_name月度趋势) monthly_pct.to_excel(writer, sheet_name月度环比)如果要做可视化一般与Matplotlib搭配使用import matplotlib.pyplot as plt monthly_city.plot(figsize(10, 5), title各城市月度销售额趋势) plt.ylabel(销售额) plt.tight_layout() plt.show()monthly_city.plot底层会调用Matplotlib接口比直接操作Matplotlib对象省事很多。我在实际项目中80%的图表都是这样快速画出来的只有需要精细定制时才回到Matplotlib原生API。6. 常见问题与排查技巧实录6.1 高频报错与解决方案很多错误来源于对Pandas和NumPy行为理解不深。把高频问题和排查方法整理成一张速查表方便直接用。报错信息 原因 解决方案 SettingWithCopyWarning 链式赋值修改视图 用 .loc 显式赋值或先 .copy() ValueError: The truth value of a Series 用 and/or 连接多个条件 改成 和 |条件加括号 TypeError: unsupported operand... 数据类型不一致 先 astype 统一类型 MemoryError 数组或DataFrame过大 降dtype、分块读取、删无用列 KeyError: xxx 列名拼写错误或不存在 检查 df.columns或使用 rename UserWarning: failed to initialize numpy 环境问题Python解释器不对 检查 which python 和 pip 归属SettingWithCopyWarning值得单独说。它的典型场景是df_sub df[df[城市] 北京] df_sub[折扣] 0.9 # 发出警告原因是对DataFrame切片得到的是视图对它赋值时Pandas不确定会不会影响原表于是给出警告。正确做法是df_sub df[df[城市] 北京].copy() df_sub[折扣] 0.9养成好习惯任何子集DataFrame只要后续要修改第一步就.copy()。6.2 数据类型转换的三个坑Pandas数据类型转换是最容易出问题的地方。第一个坑是astype(int64)遇缺失值直接报错这个前面已经提到用可空整数类型Int64解决。第二个坑是字符串类型的时间转日期。pd.to_datetime对标准格式识别很好但遇到2024/01/02和02/01/2024这种不同格式混在一起就会识别错。建议用format参数显式指定df[日期] pd.to_datetime(df[日期], format%Y/%m/%d)第三个坑是Excel读取时数字可能被读成字符串比如带千分位逗号的1,200。处理时先去掉逗号再转数值df[金额] df[金额].str.replace(,, ).astype(float)6.3 性能优化经验数据量上了百万行Pandas的一些操作开始变慢。我的经验是抓几个大头读取时只读需要的列pd.read_csv(data.csv, usecols[日期, 城市, 销量])尽量不要用apply改用向量化操作或np.where及时清理无用列df.drop(columns[tmp])如果数据量实在太大考虑用chunk分块读取或转到DuckDB处理还有一个非常实用的小技巧df.info(memory_usagedeep)能看到DataFrame的真实内存占用排查内存问题第一步先跑这个。6.4 一个小众但好用的技巧pd.merge做表关联时很多新手不知道可以同时关联多个键。例如一个订单表要关联城市表和品类表可以用列表传多个连接键merged pd.merge( orders, city_info, on[城市, 月份], howleft )两个表如果有相同列名且不用作连接键Pandas会自动加_x和_y后缀。如果不想要可以在merge后用rename改回来。这个小细节能省不少事。7. 学习路径与后续扩展建议学NumPy和Pandas最忌讳的是只看教程不动手。我的建议是装好环境后用真实数据比如自己手机里的通讯录导出成CSV或者网上下载公开销售数据完整跑一遍分析流程从读数据到出结果全程自己写。遇到报错先尝试自己解决再查文档、查社区讨论。这两个库的官方文档质量很高。NumPy的User Guide和Pandas的User Guide都配有大量示例强烈建议作为案头参考。如果英语阅读有压力先学会用搜索解决具体问题也行但官方文档一定要收藏。再往后可以接触这些方向学习方向 涉及工具/库 能解决什么问题 数据可视化 Matplotlib、Seaborn 把分析结果变成图 时间序列分析 statsmodels、prophet 做趋势预测 数据清洗ETL pyjanitor、pandera 更规范的清洗流程 大数据量计算 Dask、Polars、DuckDB 超过单机内存的数据 机器学习入门 scikit-learn 特征工程和建模每个方向都会用到NumPy和Pandas的基础能力所以基础打牢非常关键。我在日常工作里有个体会与其囤一堆高级操作技巧不如把最常用的几个操作练到条件反射比如groupby、merge、pivot_table、fillna、astype、to_datetime、rolling。这些组合起来可以解决数据清洗和分析中80%以上的问题。最后分享一个我实测好用的习惯每次拿到一个新数据集第一件事不是急着分析而是先跑一遍df.info()、df.describe()、df.isna().sum()、df.duplicated().sum()这四条命令。花不到一分钟但能让你对数据的健康程度心里有数。很多时候分析结果异常反过来查都是数据质量问题而上面四条基本能帮你把大部分坑提前排掉。数据分析这事思路比工具重要数据质量比模型重要动手实践比收藏教程重要。NumPy和Pandas只是工具真正创造价值的是你用它们解决问题的能力。多拿真实数据练手踩过的坑、解决过的报错都会成为你做判断时最可靠的直觉。