ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Pandas数据读取与核心操作全解析:从入门到实战

Pandas数据读取与核心操作全解析:从入门到实战 1. 从“脏果君”的分享说起为什么Pandas是数据人的瑞士军刀最近在B站上刷到一位叫“脏果君”的UP主分享的Pandas入门视频内容挺扎实的主要讲的是怎么用Pandas读取数据和进行基础操作。这让我想起自己刚接触数据分析那会儿面对一堆Excel、CSV、数据库导出的文件手忙脚乱效率极低。Pandas的出现就像给数据工作者配上了一把趁手的瑞士军刀它把那些繁琐、重复的数据“脏活累活”变得优雅而高效。无论是金融分析、市场调研还是学术研究、日常报表只要你需要和表格数据打交道Pandas几乎是你绕不开的工具。这篇文章我就结合“脏果君”视频里提到的一些核心点以及我这些年踩过的坑和积累的经验为你系统地拆解Pandas的数据读取与核心操作目标是让你看完就能上手解决80%的日常数据处理需求。2. 数据读取你的分析始于正确的第一步读取数据是数据分析的起点但这第一步如果没走好后面可能全是坑。Pandas支持读取的数据源非常丰富从简单的文本文件到复杂的数据库连接几乎无所不包。这里我们重点聊聊最常用的几种并补充一些官方文档里不常提但实践中至关重要的细节。2.1 文本文件CSV与TXT的读取陷阱与技巧pd.read_csv()可能是Pandas中使用频率最高的函数之一但它远不止pd.read_csv(data.csv)这么简单。import pandas as pd # 最基础的读取 df pd.read_csv(data.csv) print(df.head())关键参数深度解析编码问题 (encoding): 这是新手第一个大坑。中文Windows系统生成的CSV文件默认编码可能是gbk或gb2312而Mac/Linux或一些导出工具常用utf-8。如果读取时遇到UnicodeDecodeError首要尝试的就是指定编码。# 尝试不同的编码 try: df pd.read_csv(data.csv, encodingutf-8) except UnicodeDecodeError: df pd.read_csv(data.csv, encodinggbk) # 更稳妥的做法是先用 chardet 库探测编码 import chardet with open(data.csv, rb) as f: result chardet.detect(f.read(10000)) # 读取前10000字节进行探测 encoding result[encoding] df pd.read_csv(data.csv, encodingencoding)分隔符与表头 (sep,header): 不是所有逗号分隔的文件都叫CSV。有时你会遇到制表符分隔的TSV文件 (sep\t)或者分号分隔的文件常见于欧洲地区因为逗号用作小数点。header参数用于指定哪一行作为列名。header0默认表示第一行headerNone表示文件没有表头Pandas会自动生成整数列名0, 1, 2...这时你可以用names参数传入一个列表来指定列名。# 读取无表头、制表符分隔的文件并指定列名 df pd.read_csv(data.tsv, sep\t, headerNone, names[日期, 销售额, 区域])处理“脏数据” (skiprows,skipfooter,na_values): 现实中的数据往往不完美。文件开头可能有几行注释说明结尾可能有汇总行。skiprows和skipfooter可以跳过这些行。na_values参数可以指定哪些字符串应被识别为缺失值NaN比如把“N/A”、“-”、“NULL”都统一识别为NaN。df pd.read_csv(dirty_data.csv, skiprows3, # 跳过前3行如文件说明 skipfooter2, # 跳过最后2行如总计行需要安装python引擎pip install python-engine enginepython, na_values[N/A, -, NULL, ])大文件读取优化 (chunksize,usecols,dtype): 当你的CSV文件有几个G甚至更大时直接读入内存会导致崩溃。chunksize参数允许你分块读取每次迭代处理一块数据常用于数据清洗或聚合后写入新文件。# 分块读取并计算每块的平均值最后再汇总 chunk_iter pd.read_csv(huge_data.csv, chunksize100000) # 每次读10万行 total_sum 0 total_count 0 for chunk in chunk_iter: total_sum chunk[value].sum() total_count len(chunk) overall_mean total_sum / total_count另外在读取前如果知道某些列用不到用usecols指定需要的列名或索引能极大减少内存占用。预先通过dtype参数指定列的数据类型如{col1: int32, col2: category}也能提升读取速度和节省内存。2.2 Excel文件跨越.xlsx与.xls的鸿沟pd.read_excel()用于读取Excel文件它依赖于openpyxl用于.xlsx或xlrd用于较旧的.xls库。确保你已经安装了它们pip install openpyxl xlrd。# 读取Excel文件 df pd.read_excel(data.xlsx, sheet_nameSheet1) # 指定工作表名 # 或者 df pd.read_excel(data.xlsx, sheet_name0) # 指定工作表索引从0开始实操心得多工作表处理sheet_name参数可以是None这样会返回一个字典键是工作表名值是对应的DataFrame。pd.read_excel(data.xlsx, sheet_nameNone)。读取特定区域使用usecols和skiprows可以精确控制读取Excel的哪个区域这在报表格式不固定时非常有用。性能注意读取大型Excel文件比CSV慢得多内存占用也大。如果可能建议让数据提供方导出为CSV或者自己在Excel中另存为CSV再处理。2.3 其他数据源数据库与剪贴板Pandas的read_sql函数可以方便地从数据库读取数据你需要先建立数据库连接通常使用sqlalchemy库。from sqlalchemy import create_engine # 创建连接引擎这里以SQLite为例 engine create_engine(sqlite:///my_database.db) # 执行SQL查询并直接得到DataFrame query SELECT * FROM sales WHERE date 2023-01-01 df pd.read_sql(query, engine)一个非常实用但常被忽略的功能是pd.read_clipboard()。你可以直接从Excel或网页表格中复制数据然后在Python中运行这行代码数据就直接进入DataFrame了对于快速测试和小数据搬运来说效率奇高。3. 数据初窥与清洗看清数据的“素颜”数据读进来后别急着分析。先花时间了解它的结构和质量这个过程叫数据探索EDA和数据清洗。这是保证后续分析结果可靠性的基石。3.1 快速了解你的数据# 查看数据形状行数列数 print(df.shape) # 查看前5行和后5行 print(df.head()) print(df.tail()) # 查看索引、列名和数据类型 print(df.info()) # 查看数值型列的统计摘要计数、均值、标准差、最小值、四分位数、最大值 print(df.describe()) # 查看对象字符串型列的统计摘要 print(df.describe(include[object]))df.info()是你的好朋友它能一眼告诉你每列的非空值数量、数据类型以及内存占用。如果某列的非空计数远小于总行数说明缺失严重。如果数据类型是object通常意味着是字符串或者混合了数字和字符串需要警惕。3.2 处理缺失值不是所有空白都叫NaN现实数据中缺失值无处不在。Pandas用NaNNot a Number表示缺失值。查找缺失# 检查整个DataFrame的缺失情况 print(df.isnull().sum()) # 每列缺失值数量 print(df.isnull().sum().sum()) # 总缺失值数量 # 可视化缺失需要matplotlib/seaborn import seaborn as sns sns.heatmap(df.isnull(), cbarFalse, cmapviridis)处理缺失的几种策略删除如果缺失行占比很小或者缺失列不重要可以直接删除。df_dropped df.dropna() # 删除任何包含NaN的行 df_dropped_col df.dropna(axis1) # 删除任何包含NaN的列 df_dropped_subset df.dropna(subset[重要列1, 重要列2]) # 仅在指定列有NaN时才删除行填充用某个值替换NaN。df_filled df.fillna(0) # 用0填充所有NaN df_filled_mean df[数值列].fillna(df[数值列].mean()) # 用该列均值填充 df_filled_ffill df.fillna(methodffill) # 用前一个有效值向前填充 df_filled_bfill df.fillna(methodbfill) # 用后一个有效值向后填充注意填充方法的选择需要结合业务逻辑。用均值填充可能扭曲分布前向/后向填充在时间序列数据中很常用。3.3 处理重复值数据中的“影分身”重复行会干扰统计结果的准确性。# 检查重复行基于所有列 print(df.duplicated().sum()) # 删除重复行保留第一条 df_dedup df.drop_duplicates() # 基于特定列检查重复 df_dedup_subset df.drop_duplicates(subset[用户ID, 日期], keeplast) # 基于用户ID和日期去重保留最后一条3.4 数据类型转换让数据“名正言顺”错误的数据类型会导致计算错误或性能下降。常见转换# 将字符串转换为数值无法转换的会变成NaN df[价格] pd.to_numeric(df[价格], errorscoerce) # 将字符串转换为日期时间 df[日期] pd.to_datetime(df[日期], format%Y/%m/%d) # 指定格式能加速转换 # 将分类文本转换为category类型节省内存并提升速度 df[城市] df[城市].astype(category) # 将整数转换为更节省内存的类型 df[小整数列] df[小整数列].astype(int8)经验之谈对于只有有限几个取值的字符串列如性别、省份、产品类别转换成category类型是提升Pandas性能最立竿见影的方法之一尤其是在进行分组groupby操作时。4. 数据选择与过滤精准定位你的目标在DataFrame中选取你需要的数据子集是进行一切分析的前提。Pandas提供了多种灵活且强大的索引方式。4.1 基于标签和位置的索引.loc与.iloc这是最核心的两个索引器必须彻底分清。.loc[]基于标签label进行选择。索引器里的内容可以是单个标签、标签列表、标签切片、布尔数组。.iloc[]基于整数位置integer position进行选择。索引器里的内容是整数、整数列表、整数切片。# 假设df的索引是默认的0,1,2...列名是[A, B, C, D] # .loc 示例 df.loc[0, A] # 选择索引为0列名为‘A’的单个值 df.loc[[0, 2, 4], [A, C]] # 选择多行多列 df.loc[0:5, A:C] # 切片选择注意.loc的切片是**包含**结束点的这与Python列表不同。 df.loc[df[A] 10, :] # 布尔索引选择A列大于10的所有行所有列 # .iloc 示例 df.iloc[0, 1] # 选择第0行第1列即‘B’列的值 df.iloc[[0, 2], [1, 3]] # 选择第0、2行第1、3列 df.iloc[0:5, 1:4] # 切片选择遵循Python惯例不包含结束点5和4常见踩坑点当你重置了索引df.reset_index()后原来的索引变成了一列普通数据此时用.loc[0]选择的是新索引为0的行而不是原来索引为0的行。务必清楚你当前DataFrame的索引是什么。4.2 条件过滤用布尔表达式筛选数据这是数据分析中最常用的操作之一。原理是先生成一个布尔序列True/False然后用这个序列去索引DataFrame。# 单条件筛选 high_sales df[df[销售额] 10000] # 多条件组合必须用括号 condition (df[销售额] 10000) (df[区域] 华东) # “与”操作 condition (df[销售额] 10000) | (df[利润率] 0.1) # “或”操作 condition ~(df[区域].isin([华北, 华南])) # “非”操作不在列表中 filtered_df df[condition] # 使用query方法语法更简洁尤其是列名包含空格时 filtered_df df.query(销售额 10000 and 区域 华东)4.3 字符串与时间序列的过滤对于字符串列可以使用.str访问器配合字符串方法。# 筛选产品名称包含“手机”的行 df_phone df[df[产品名].str.contains(手机, naFalse)] # naFalse处理NaN值 # 筛选以“A”开头的客户ID df_A df[df[客户ID].str.startswith(A)]对于时间序列.dt访问器是利器。# 假设‘日期’列是datetime类型 df_2023 df[df[日期].dt.year 2023] # 筛选2023年的数据 df_q1 df[df[日期].dt.quarter 1] # 筛选第一季度数据 df_weekend df[df[日期].dt.dayofweek 5] # 筛选周末数据5周六6周日5. 数据变形与核心操作让数据为你所用清洗和筛选之后就到了施展Pandas魔法的核心阶段对数据进行聚合、转换、重塑以提取信息。5.1 分组聚合groupby的威力groupby是Pandas的灵魂操作它遵循“拆分-应用-合并”的模式。# 按‘区域’分组计算‘销售额’的平均值 grouped df.groupby(区域)[销售额].mean() print(grouped) # 按多列分组并进行多重聚合 agg_result df.groupby([区域, 产品类别]).agg({ 销售额: [sum, mean, count], 利润: sum }) print(agg_result) # 这会得到一个多级索引的DataFrame高级技巧transform与applytransform返回一个与原始分组数据形状相同的Series或DataFrame。常用于组内标准化、计算组内排名。# 计算每个区域内的销售额Z-score标准化 df[销售额_zscore] df.groupby(区域)[销售额].transform(lambda x: (x - x.mean()) / x.std())apply更灵活可以对每个分组应用任意函数但返回结果的形状和类型由函数决定。# 找出每个区域销售额最高的前两名产品 def top2(group): return group.nlargest(2, 销售额) top2_by_region df.groupby(区域).apply(top2)性能警告apply是灵活的但通常比内置的聚合函数如sum,mean慢。如果可以用内置函数或transform实现优先使用它们。5.2 数据合并concat,merge,join当数据来自多个来源时需要将它们合并。pd.concat主要用于沿轴行或列堆叠多个结构相似的DataFrame。df1 pd.DataFrame({A: [A0, A1], B: [B0, B1]}) df2 pd.DataFrame({A: [A2, A3], B: [B2, B3]}) result pd.concat([df1, df2], ignore_indexTrue) # 纵向堆叠忽略原索引pd.merge/df.merge基于一个或多个键key将两个DataFrame横向连接类似于SQL的JOIN操作。left pd.DataFrame({key: [K0, K1, K2], A: [A0, A1, A2]}) right pd.DataFrame({key: [K0, K1, K3], B: [B0, B1, B3]}) # 内连接默认 inner_merge pd.merge(left, right, onkey) # 左连接 left_merge pd.merge(left, right, onkey, howleft) # 外连接 outer_merge pd.merge(left, right, onkey, howouter)how参数是关键inner交集、left左表全部、right右表全部、outer并集。df.join是merge的简化版主要用于基于索引进行合并。5.3 数据透视表pivot_table数据透视表是进行多维分析的强大工具可以快速对数据进行汇总。# 创建一个简单的透视表以‘区域’为行‘产品类别’为列对‘销售额’进行求和 pivot df.pivot_table(values销售额, index区域, columns产品类别, aggfuncsum, fill_value0, # 填充NaN为0 marginsTrue) # 添加总计行/列 print(pivot)aggfunc可以是字符串如sum,mean,count、函数或函数列表。fill_value用于处理缺失的组合这在实践中非常重要。5.4 向量化操作与避免循环Pandas的底层是NumPy其核心优势在于向量化操作。这意味着你应该尽量避免在DataFrame上使用Python的for循环。低效做法新手常见for i in range(len(df)): if df.loc[i, 销售额] 1000: df.loc[i, 等级] 高 else: df.loc[i, 等级] 低高效做法向量化df[等级] np.where(df[销售额] 1000, 高, 低) # 或者使用更复杂的条件 conditions [ (df[销售额] 1000) (df[利润率] 0.2), (df[销售额] 500), (df[销售额] 500) ] choices [A级, B级, C级] df[评级] np.select(conditions, choices, default未知)向量化操作通常比循环快几十甚至上百倍尤其是在数据量大的时候。6. 实战演练一个完整的数据处理小案例让我们用一个模拟的销售数据集串联起上面讲到的多个操作。假设我们有一个sales_data.csv文件包含订单ID、日期、区域、产品类别、销售额、利润等字段。目标分析2023年各区域、各产品类别的销售表现找出销售额高但利润率低的“问题产品”。import pandas as pd import numpy as np # 1. 读取数据 df pd.read_csv(sales_data.csv, parse_dates[日期]) # 读取时直接解析日期 # 2. 数据初探与清洗 print(数据形状:, df.shape) print(df.info()) print(df.isnull().sum()) # 假设发现‘利润’有少量缺失用该产品类别下的平均利润填充 df[利润] df.groupby(产品类别)[利润].transform(lambda x: x.fillna(x.mean())) # 3. 筛选2023年数据 df_2023 df[df[日期].dt.year 2023].copy() # 使用.copy()避免后续操作出现SettingWithCopyWarning # 4. 计算利润率 df_2023[利润率] df_2023[利润] / df_2023[销售额] # 5. 分组聚合计算各区域-产品类别的总销售额、平均利润率 grouped_analysis df_2023.groupby([区域, 产品类别]).agg({ 销售额: sum, 利润率: mean }).round(4) # 保留4位小数 grouped_analysis grouped_analysis.reset_index() # 将多级索引变回列 # 6. 定义“问题产品”销售额高于中位数但利润率低于0.1 sales_median grouped_analysis[销售额].median() problem_products grouped_analysis[ (grouped_analysis[销售额] sales_median) (grouped_analysis[利润率] 0.1) ] print(需要关注的问题产品) print(problem_products.sort_values(销售额, ascendingFalse)) # 7. 可选数据透视表可视化展示 pivot_sales df_2023.pivot_table(values销售额, index区域, columns产品类别, aggfuncsum, fill_value0) print(\n2023年区域-产品销售额透视表) print(pivot_sales)这个案例涵盖了读取、清洗、过滤、计算新列、分组聚合、条件筛选等多个核心步骤是一个典型的数据分析工作流。7. 性能优化与常见“坑点”备忘录当你开始处理更大规模的数据时性能和内存会成为瓶颈。这里有一些进阶提示选择合适的数据类型如前所述将字符串列转为category将大整数转为int32/int16能显著节省内存。使用query方法进行复杂过滤对于复杂的多条件布尔过滤df.query()的语法更清晰有时性能也略好于直接在括号内写布尔表达式。警惕SettingWithCopyWarning这是一个警告不是错误但忽视它可能导致意想不到的结果。它通常在你尝试修改一个可能是原始DataFrame切片副本的DataFrame时出现。最安全的做法是当你明确要修改一个筛选后的子集时使用.copy()方法显式创建副本。# 可能引发警告的做法 subset df[df[A] 0] subset[B] 1 # 可能触发SettingWithCopyWarning # 安全的做法 subset df[df[A] 0].copy() subset[B] 1 # 安全大文件迭代处理对于无法一次性读入内存的文件坚持使用chunksize参数进行分块读取和处理。考虑其他工具当数据量巨大数十GB以上时Pandas可能力不从心。可以考虑Dask一个并行计算库其DataFrame API与Pandas高度相似可以处理超出内存的数据。Modin通过一行代码import modin.pandas as pd替换import pandas as pd利用多核CPU加速Pandas操作对read_csv和groupby等操作加速明显。直接使用数据库对于超大规模数据在数据库内完成聚合和筛选使用SQL只将最终结果集读入Pandas往往是最高效的方案。Pandas的强大在于其丰富而一致的API一旦掌握了这些核心操作和思维模式你处理数据的效率将发生质的变化。它不仅仅是工具更是一种组织、思考和操作数据的范式。从“脏果君”的视频入门是个很好的开始但真正的精通来自于在无数真实、杂乱的数据集上反复练习和踩坑。记住遇到报错时仔细阅读错误信息善用df.head()、df.info()、df.isnull().sum()来查看数据状态大部分问题都能迎刃而解。
返回列表