Pandas构建DataFrame全攻略:从安装到性能优化

发布时间:2026/9/9 15:12:24
Pandas构建DataFrame全攻略:从安装到性能优化 做Python数据分析绕不开的一个东西就是DataFrame。你可以把它理解成一张放在内存里的Excel表格也可以把它理解成一张不依赖数据库的SQL表——行是记录列是字段每个列还各自带着自己的数据类型。Pandas就是操作这张表的工具箱而构建DataFrame是使用Pandas的第一步也是后续所有清洗、筛选、聚合、建模操作的地基。这篇文章我会从环境安装讲起把构建DataFrame的几种常用方式、构建之后怎么快速查看和调整数据、以及我在实际项目里踩过的坑全部梳理一遍。内容对刚入门的新手很友好同时也适合已经用过一段时间、想在构建阶段就避开性能隐患的朋友参考。说白了你把这篇文章当作一份“建表前、建表中、建表后”的完整操作手册就行。1. 动手之前环境准备与Pandas安装1.1 PyCharm里装Pandas的几种方式我身边很多朋友第一个接触Python的IDE就是PyCharm说实话用它装Pandas也是坑最多的环节。最常见的报错是“ModuleNotFoundError: No module named pandas”不是你没装而是装到了别的解释器里。PyCharm底部或者右侧有个Python Interpreter设置打开SettingsMac上是Preferences找到Project: xxx → Python Interpreter这里显示的是当前项目用的解释器路径你在这个界面点加号搜索pandas然后点Install Package装完后才会出现在当前项目里。如果你的PyCharm里Terminal可以直接用我更推荐直接用pip命令装。在PyCharm底部打开Terminal执行下面这条命令pip install pandas openpyxl把openpyxl一起装了是有讲究的。openpyxl是Pandas读写Excel文件的后端引擎如果你之后要用pd.read_excel或者df.to_excel少了它就会直接报ImportError。我见过太多人装完pandas后跑read_excel报错然后一脸懵地去搜解决方案其实就是在安装时少带了一个包。另外还要提醒一点千万别用系统自带的Python环境直接pip install尤其是macOS和Linux系统Python归系统管你往里面强灌包很容易把环境搞坏。我一律建议在项目里创建虚拟环境venv或conda把依赖隔离在项目内部这样就算装错了、版本冲突了删掉重建也就一两分钟的事。1.2 版本适配与依赖包选择版本这块很多人上来就问“Python 3.10到底该配哪个版本的Pandas”。我的经验是如果用Python 3.10建议Pandas版本至少装1.5以上直接装最新的2.x也完全没问题。Python 3.11、3.12同理直接用2.x版本最省心。如果你还在用Python 3.7或3.8这种老版本那Pandas会被限制在1.5.x装不了2.x这时候就别硬升级了老版本搭配老Pandas是正常操作。安装的时候还可以顺手把numpy一起装了Pandas底层依赖NumPy虽然pip在装Pandas时会自动带上NumPy但很多时候你后续做数值计算也会直接用NumPy所以建议显式安装一遍保证版本是自己预期的pip install numpy pandas依赖这里还有一个容易忽略的点如果Pandas在读取Parquet格式文件时报错提示找不到引擎那说明你缺了pyarrow或者fastparquet。这个其实不是Pandas本身的问题但实际战斗中经常遇到我把命令放在这你可以提前装好pip install pyarrowpyarrow装上后Pandas读写Parquet和Feather格式就都能用了这两个格式我在后面第4章会专门展开讲。2. 构建DataFrame的六种常用姿势2.1 字典构建最直观、最常用的方式字典构建DataFrame是用的最多、也最符合直觉的方式。核心思想是键是列名值是该列的完整数据。比如你要做一个学生信息表直接写成这样import pandas as pd df pd.DataFrame({ 姓名: [张三, 李四, 王五], 年龄: [25, 30, 35], 城市: [北京, 上海, 广州] }) print(df)输出就是一张三行三列的表列名依次是姓名、年龄、城市。这种方式特别适合手工录入小数据、或从接口拿到JSON后转成DataFrame因为JSON本身就是字典嵌套的转过来几乎不需要额外处理。字典构建的关键点在于每个键对应的值长度必须一致。如果有一个键的值是3个元素另一个键的值是2个元素Pandas不会自动帮你补全而是直接抛ValueError意思是“所有数组长度必须相同”。这是新手最容易踩的坑。但凡事有例外。如果你在字典里传的是Series而不是列表长度不一样时Pandas会用索引对齐缺失的部分自动补NaN。比如有一列是pd.Series([1, 2, 3], index[0, 1, 2])另一列是pd.Series([4, 5], index[0, 1])Pandas会生成两行第二列第二行是NaN。这种特性在某些数据对齐场景下非常有用但如果你刚开始学我建议先记住“列表构建必须等长”避免在数据上出意外。2.2 列表和元组构建数据天然是行列表时用这个有时候你的数据不是按列来的而是按行来的。比如爬虫抓下来的数据每行是一个元组这时候用字典去配就不方便了直接用“元组列表”构建更顺手data [ (张三, 25, 北京), (李四, 30, 上海), (王五, 35, 广州) ] df pd.DataFrame(data, columns[姓名, 年龄, 城市])这里columns参数的作用是给每列起名字顺序必须和元组内的元素顺序保持一致否则列名和数据就错位了。我见过有人在这里把顺序写反结果“姓名”列里全是数字还以为是数据源出了问题排查了半天才发现是列名顺序错了。如果你手里的数据是“列表的列表”也就是嵌套列表用法和元组列表一模一样只是把圆括号换成方括号。从CSV或数据库导出的数据往往也是这种结构所以这个姿势在数据处理脚本里非常常见。还有一种衍生的用法是用zip把多个独立列表组合成行数据。比如你有三个列表分别存姓名、年龄和城市就可以这样拼names [张三, 李四, 王五] ages [25, 30, 35] cities [北京, 上海, 广州] df pd.DataFrame(zip(names, ages, cities), columns[姓名, 年龄, 城市])zip在这里是惰性求值的也就是说它不会一次性生成所有数据而是按需产出数据量大的时候内存压力小一点。不过这个写法有个隐性问题如果三个列表长度不一致zip会默默截断到最短的那个不会报错。所以我一般只在确认数据等长时才用zip否则宁可多写几行用字典或元组列表来构建至少错误能立刻暴露出来。2.3 从NumPy数组构建大规模数值计算的标配做数据分析的多多少少会接触NumPy。当你有一个二维NumPy数组时构建DataFrame几乎是零成本转换import numpy as np data np.random.randn(1000, 5) # 1000行5列的随机数 df pd.DataFrame(data, columns[A, B, C, D, E])这种方式最适合模拟数据、批量生成指标列以及把计算好的NumPy结果装进DataFrame里做进一步分析。性能上NumPy数组本身就是连续内存的数组构建DataFrame时不需要逐行拷贝所以速度非常快。需要注意NumPy数组里所有元素必须是同一类型。如果你有一列是字符串另一列是数字那要么先拆分处理要么用object类型的数组绕过去。我个人的经验是——能用纯数值就用纯数值混合类型会让后续的groupby、排序、绘图都变慢而且很多算法模型根本不吃字符串。如果你想要快速生成一个全是0或全是1的DataFrame也可以配合NumPy的zeros和onesdf pd.DataFrame(np.zeros((10, 3)), columns[x, y, z])这在初始化矩阵、搭建回归实验时特别好用。2.4 从文件构建CSV、Excel、Parquet、Feather实际项目里DataFrame九成以上是从外部文件读进来的。最基础的是CSVdf pd.read_csv(data.csv)Excel则需要engine支持提前装好openpyxldf pd.read_excel(data.xlsx, sheet_nameSheet1)读CSV时我习惯显式指定encoding参数默认编码在不同系统上表现不一致Windows上经常出现中文乱码。遇到乱码不要慌试试加参数encodingutf-8或者encodinggbk总有一个能跑通。读Excel时有个小技巧用参数usecols指定需要的列用nrows指定读取行数能用这两个参数过滤的就在读取时过滤不要等到读进内存再处理。数据量大时这个习惯能帮你省下大量内存和时间。比如你只想看前1000行里的两列df pd.read_excel(data.xlsx, usecols[姓名, 年龄], nrows1000)Parquet和Feather是两种列式存储格式现在在大数据生态里用得越来越多。Parquet的优势是压缩率高且兼容性强Spark、Hive、DuckDB都能直接读Feather的优势是读写速度极快适合在分析过程中做中间结果的临时存储。如果装了pyarrowPandas读它们很简单df pd.read_parquet(data.parquet) df pd.read_feather(data.feather)我自己的习惯是正式数据落地用Parquet中间分析结果临时缓存用Feather。这两个格式对比CSV和Excel在几百MB甚至几个GB的场景下差别非常明显CSV读10分钟的文件Parquet可能几秒就加载完了。后面第4章我还会再展开讲。2.5 从SQL和API接口构建从数据库读取是构建DataFrame的另一个高频场景。Pandas提供了read_sql配合sqlite3或者SQLAlchemy就能直接执行查询并返回DataFrameimport sqlite3 conn sqlite3.connect(mydb.db) df pd.read_sql(SELECT 姓名, 年龄 FROM users WHERE 年龄 20, conn) conn.close()这种方式最大的好处是数据筛选、聚合在数据库端就完成了Python这边拿到的已经是精简后的结果不会把整张表都灌进内存。如果你公司的数据在MySQL或PostgreSQL里连接串改成SQLAlchemy的格式就行本质上一样。从API接口构建DataFrame也很常见。接口返回的JSON往往是“字典列表”或“嵌套字典”直接传到pd.DataFrame就能用import requests resp requests.get(https://api.example.com/users) data resp.json() # 假设是列表每个元素是一个用户字典 df pd.DataFrame(data)这里最大的坑是接口返回的数据结构不规整有的字段缺失、有的是嵌套对象。我的做法是先df.head()看几行再用df.info()看字段类型最后再统一做缺失值填充或字段拆分。一上来就想着数据有多规整多半会翻车。2.6 空DataFrame的构建与骨架预定义有时候你需要先搭一个“空壳”DataFrame然后在循环里一行一行往里填数据这种场景常见于动态拼接报表、逐步跟踪指标。构建空DataFrame的写法是df pd.DataFrame(columns[日期, 销售额, 利润])之后用concat或者loc往里面添加行。这里我要强烈建议如果行数可以预判先预分配好容量或直接存列表再统一构建不要在循环里反复用concat追加。为什么因为concat每次都会生成一个新DataFrame行数一多性能会急剧下降几百次循环还好几万次就会卡到你怀疑人生。records [] for day in range(10000): records.append({日期: day, 销售额: day * 3, 利润: day}) df pd.DataFrame(records)这个“先收集后构建”的模式比在循环里不断concat要快几个数量级也是我在生产脚本里一直在用的写法。3. 构建完成后必须掌握的查看与清洗操作3.1 用head、info、shape快速摸清数据结构DataFrame构建出来不代表数据就没问题了。我拿到一个新DataFrame后的固定动作是三件套df.head()、df.info()、df.shape。head()看前几行长什么样默认5行注意默认只展示前5行不代表全表只有5行很多新手看到5行就以为数据没读全其实是你没看tail()。info()则会把每列的非空数量、数据类型、内存占用一次性列出来是判断数据是否完整的利器df.info()output会显示类似“RangeIndex: 1000 entries, 0 to 999”和“Data columns (total 5 columns)”这样的信息中间还会标出每列的类型。如果某列显示object而不是int64或float64那说明Pandas没有把它识别成数值类型需要检查是不是有脏数据混进去了。shape返回一个元组(行数, 列数)通常在脚本里用来快速做数据量判断。我写脚本时经常先加一个assert比如assert df.shape[0] 0如果读到空表直接报错避免后续算出一个全是NaN的结果还不知道哪里出了问题。3.2 数据类型转换astype和to_numeric的正确用法数据类型转换是我在日常问题里被问得最多的一个点。数据读进来之后最常见的问题是“数值列被识别成了object”。为什么多半是列里有空字符串、中文逗号、百分号之类的脏字符。这时候直接用astype(int64)会直接报错。解决办法是用pd.to_numeric配合errorscoerce把无法转成数字的内容统一变成NaNdf[销售额] pd.to_numeric(df[销售额], errorscoerce)之后你可以再用dropna把含NaN的行剔除或者用fillna填充。这种处理方式最大的好处是脏数据不会让整个脚本崩溃而是被显式标记出来你再决定是删是补。对于那些已经是数值、但类型不对的列用astype就足够了df[年龄] df[年龄].astype(int64) df[注册日期] pd.to_datetime(df[注册日期])关于astype和to_numeric的选择我的经验是如果你确定数据干净用astype如果数据来自外部文件、爬虫或用户输入一律用to_numeric再手动处理失败项。安全永远比省事重要。3.3 删除列和去重drop与drop_duplicates实操构建完成后经常会发现有些列根本用不上这就要用到drop了。最常用的写法是指定列名df df.drop(columns[备注, 内部编码])也可以按索引删行df df.drop(index[0, 2]) # 删除第0行和第2行这里我特别想说一下inplaceTrue的问题。网上很多教程喜欢写df.drop(columns[x], inplaceTrue)但Pandas官方现在已经不推荐inplace了我们在生产代码里也基本不用。原因是inplace很容易造成链式赋值的隐性问题而且行为有时候不如赋值语句清晰。建议统一写成df df.drop(...)重新赋值逻辑一眼看明白也不容易踩到SettingWithCopyWarning。去重这块对应很多人问的“如果指定两列的值均相同则取第一条数据即可”其实就是drop_duplicates的subset参数df df.drop_duplicates(subset[姓名, 城市], keepfirst)keepfirst是默认行为表示保留第一次出现的行后面的重复项全部删除keeplast则保留最后一次出现的行。如果你想直接把重复行全部删掉不保留任何一条可以用keepFalse。还有一个容易忽略的点subset不写默认是按照所有列去重也就是说必须所有列都完全相同才算重复。实际业务里往往只需要根据关键几列去重比如同一个用户一天只保留一条登录记录那就应该subset[用户ID, 日期]。按哪些列去重完全取决于你的业务口径这一点非常重要。3.4 列名重命名与索引重置构建过程中经常遇到列名带空格、中文、或者从文件读进来变成“Unnamed: 0”这种奇怪名字的情况。用rename统一改名df df.rename(columns{Unnamed: 0: 序号, 姓名 : 姓名})注意rename默认不是就地修改你同样需要重新赋值或者用inplaceTrue。我的习惯依然是重新赋值保持代码可读。索引问题也很常见。用drop删完行之后索引会留下空洞比如0、1、2、5、6这样的跳跃。如果你后续要用iloc按位置取数这倒没什么影响但如果你想保持索引连续或者往DataFrame里按索引去对齐数据就必须重置索引df df.reset_index(dropTrue)dropTrue的意思是把原来的旧索引丢弃不放到新列里。如果不写dropTrue旧索引会变成一列“index”有时候也会有用但绝大多数场景我是想丢掉旧索引的。4. 用Pandas构建高性能DataFrame的4个实战技巧4.1 为什么建议用NumPy数组而不是Python列表在构建DataFrame时尤其是数据量大到千万行级别时底层数据是NumPy数组还是Python列表性能差距是肉眼可见的。我在一次处理用户行为日志时用Python列表存中间结果内存直接冲到8个GB换成NumPy数组后降到5GB以内构建时间也缩短了一大截。原因在于NumPy数组在内存里是连续存储的访问和计算都能走到底层的C代码优化而Python列表存的是对象的引用内存碎片化遍历时还要做类型判断自然慢。如果你的数据是纯数值型最佳实践是先用NumPy把计算做完最后再用pd.DataFrame包一层输出。如果你的数据是混合类型比如既有字符串又有数字那也尽量用“列表收集”代替“一行行构建DataFrame”减少中间对象的创建。4.2 Parquet和Feather大数据场景下的读写优化热搜词里有“pandas与numpy实现spark在格式parquet及语言feather等上的案例操作”这其实反映了一个趋势Pandas用户越来越需要和大数据生态互通。Parquet格式是Spark等大数据平台的默认列式存储格式Pandas读写Parquet时用pyarrow做引擎性能非常优秀df.to_parquet(output.parquet, indexFalse) df pd.read_parquet(output.parquet)这里我建议把indexFalse加上避免把默认为0开始的行索引也写进文件造成无意义的存储浪费。Feather格式则更适合“快速临时存储”。它的设计目标就是极限读写速度适合在数据分析流程中保存中间结果。比如你花了几分钟清洗好一份数据不想每次都重新跑一遍清洗逻辑就把中间结果存成feather下次直接读df.to_feather(clean_data.feather) df pd.read_feather(clean_data.feather)在我自己的项目里feather的读取速度几乎是CSV的几十倍。对于几百MB甚至上GB的中间数据用feather做缓存能省下大量重复计算时间。4.3 内存优化与copy视图问题内存优化是在处理大数据集时绕不开的话题。除了用NumPy数组还可以通过合理的数据类型来减少占用。比如一列只有0和1两种取值的整数就没必要用int64转成int8能省7倍的空间df[flag] df[flag].astype(int8)再比如状态列是几个固定字符串用category类型能大幅减少内存df[城市] df[城市].astype(category)我遇到过一列“城市”有几百个重复字符串转成category后内存直接从几百MB降到了几十MB处理速度也明显提升。还有一定不要忽视“视图与复制”的问题。Pandas里做布尔筛选后得到的可能是一个视图也可能是一个拷贝取决于操作方式。如果你接着对筛选结果做修改有时会触发SettingWithCopyWarning。这个警告翻译成人话就是你改的可能不是你想象中的那份数据。解决方法是如果你确实要修改筛选结果显式加.copy()创建独立副本sub df[df[年龄] 30].copy() sub[分组] A在你没彻底搞清楚视图和拷贝区别之前我建议所有的“筛选后再赋值”都带上.copy()这样可以避免一大批隐蔽的数据污染问题。5. DataFrame构建中避不开的5类问题和排查方法5.1 数据对不齐、列名对不上构建DataFrame时报错“ValueError: All arrays must be of the same length”是最常见的第一道坎。这个错误的原因就是你传给字典的各列表长度不一致解决方法是检查每一列的数据源把数据补全或裁剪到一致。还有一种“对不齐”更隐蔽两个Series构建DataFrame时长度不同但索引部分重叠Pandas不会报错而是自动做索引对齐结果里出现大量NaN。遇到这种情况可以先用df.isna().sum()看看每列缺了多少值再决定是填充还是丢弃。列名对不上则多半发生在改过列名之后。比如Excel里有两个同名列读进来后Pandas会自动改名成“城市”和“城市.1”如果你没注意到后面还带着一个“.1”后续操作就全乱了。解决办法是在读取阶段指定好列名或者读取后立刻检查columns列表。5.2 类型转换报错与object类型陷阱“ValueError: invalid literal for int() with base 10”是astype转换时最常报的错。一般是数据里有字符串、空值或者特殊字符。正确处理姿势是先看数据print(df.loc[df[销售额].isna() | df[销售额].astype(str).str.contains([^0-9.])])把可疑行抓出来再决定是清洗还是删除。多数情况下pd.to_numeric(errorscoerce)就能把问题变成NaN然后再统一处理。object类型还有一个隐藏雷区当某一列是object类型时即使它看起来全是数字groupby、排序、数值计算都会按字符串来处理结果和预期完全不同。所以构建后第一件事用info()看类型凡是数值列显示object的一律转成数值类型再走后续流程。5.3 安装版本冲突的排查思路安装Pandas时最容易遇到两个问题一个是pip install很慢或超时另一个是装完之后import时报错。前者多半是网络问题可以换成国内镜像源pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple后者则大概率是版本兼容问题。比如Python 3.10配了Pandas 1.1那就会有编译报错或依赖冲突。正确的排查办法是先看当前Python版本python --version再根据版本选择合适的PandasPython 3.10以上直接装最新2.x老版本Python就老老实实用对应旧版。如果你在一个大项目里装来装去把环境搞乱了我的建议是不要一个个包去排查依赖直接新建虚拟环境重新来。venv和conda都可以几分钟就搞定比在乱环境里“考古”高效得多。5.4 索引混乱与乱序问题构建DataFrame时如果带了index参数或者读取时指定了某一列作为索引后续操作经常会因为索引不连续而出现“明明看着是第5行用iloc取出来却是另外一条”的问题。解决索引混乱最直接的方式是reset_index(dropTrue)。如果还想保留原来的索引信息用reset_index()不带dropTrue旧索引会变成新的一列。另外如果你在合并两个DataFrame时没注意索引对齐或者用了groupby之后没有reset_index数据顺序可能看起来是乱的。这里我的经验是只要你对行顺序有要求操作完后都执行一次reset_index(dropTrue)确保索引从0开始连续递增这样后续无论用什么方式筛选、合并都不容易被旧索引干扰。写到这里关于Pandas构建DataFrame的核心内容基本都覆盖了。最后再分享一个小技巧构建数据之前先想清楚每一列的数据类型和取值范围能省掉后面一大半清洗工作。我自己在建表时习惯先画一个简单的字段清单列名、类型、是否有缺失、允许值范围都写清楚再动手敲代码。这看起来麻烦但在数据量大了之后收益会非常明显。希望这篇文章能让你在构建DataFrame的路上少走一些弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询