1. 项目概述:为什么info()远不止一个“查看”命令
如果你用pandas处理数据超过一周,大概率已经用过DataFrame.info()这个函数了。表面上看,它就是个简单的信息摘要:打印出数据框的行列数、列名、非空值数量和数据类型。很多新手教程把它归为“数据预览”或“基本信息查看”一类,用一两句话带过。但在我处理过上百个真实的数据清洗、特征工程和模型准备项目后,我意识到,绝大多数人都严重低估了info()的价值。
它不是一个被动的“查看器”,而是一个主动的“诊断仪”。当你拿到一份陌生的、脏的、来源不明的数据时,info()是你按下的一键扫描。它输出的那几行文字,几乎能立刻告诉你这份数据的“健康状态”:哪里缺了“骨头”(缺失值),哪里“骨头”长得不对(数据类型错误),以及整体的“骨架”是否匀称(内存占用)。很多后续数小时的数据清洗和调试工作,其根源问题在第一次调用info()时就已埋下伏笔。这次,我们就彻底拆解这个看似简单却至关重要的函数,让你从“会用”升级到“精通”,真正把它变成数据分析工作流中的核心侦察兵。
2.info()的核心功能与输出全解
info()方法会向控制台打印一个DataFrame或Series的简明摘要。这个摘要信息结构固定,但每一行都暗藏玄机。我们先从一个最简单的例子开始,建立直观认识。
2.1 基础输出结构拆解
假设我们有一个简单的DataFrame:
import pandas as pd import numpy as np # 创建一个包含多种数据类型和缺失值的示例数据框 df = pd.DataFrame({ 'user_id': [101, 102, 103, 104, 105], 'name': ['Alice', 'Bob', 'Charlie', None, 'Eve'], 'age': [25, 30, 35, 40, None], 'score': [89.5, 92.0, 76.5, 88.0, 95.5], 'is_active': [True, True, False, True, False], 'join_date': pd.to_datetime(['2023-01-15', '2023-02-20', '2023-01-10', '2023-03-01', '2023-02-28']) }) print(df.info())运行后,你会看到类似下面的输出(具体格式可能因pandas版本略有不同):
<class 'pandas.core.frame.DataFrame'> RangeIndex: 5 entries, 0 to 4 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 5 non-null int64 1 name 4 non-null object 2 age 4 non-null float64 3 score 5 non-null float64 4 is_active 5 non-null bool 5 join_date 5 non-null datetime64[ns] dtypes: bool(1), datetime64[ns](1), float64(2), int64(1), object(1) memory usage: 338.0 bytes我们来逐行解读这个“诊断报告”:
<class ‘pandas.core.frame.DataFrame’>: 确认对象类型。这行看似废话,但在复杂的代码流或函数封装中,有时需要确认传入的变量确实是DataFrame而非Series或其他结构。RangeIndex: 5 entries, 0 to 4: 索引信息。这里使用的是默认的RangeIndex,共5条记录,索引从0到4。如果索引是自定义的(如时间序列索引DatetimeIndex或多级索引MultiIndex),这里会明确显示出来。注意:entries指的是行数。- 表格主体(
Data columns): 这是核心部分。#: 列的序号。Column: 列名。Non-Null Count:该列非空(非NaN/None)值的数量。这是发现缺失值最直接的地方。例如name列显示4 non-null,而总行数是5,立刻可知有1个缺失值。Dtype: 列的数据类型。pandas用object通常表示字符串或混合类型,int64/float64是数值,bool是布尔值,datetime64[ns]是日期时间。
dtypes:: 所有数据类型的汇总统计。告诉你这个DataFrame由哪些类型的列构成。快速扫一眼,可以判断数据是否“干净”。例如,如果一个应该是数值的列显示为object,通常意味着数据中混入了字符串(如“N/A”,“-”),需要清洗。memory usage: 338.0 bytes:内存使用量估算。对于小型数据可以忽略,但对于百万、千万行级别的大数据,这个数字是优化内存的起点。如果内存占用远超预期,可能意味着数据类型选择不当(比如用object存储了本可以用category存储的分类数据)。
2.2 关键参数深度解析
df.info()的默认行为已经很有用,但通过其参数,我们可以定制化这份“诊断报告”,获取更精准的信息。
verbose参数(布尔值,默认为True): 当设置为verbose=False时,info()将只输出一个精简版的摘要,省略每一列的详细信息,只显示数据类型汇总和内存使用情况。print(df.info(verbose=False))输出:
<class 'pandas.core.frame.DataFrame'> RangeIndex: 5 entries, 0 to 4 dtypes: bool(1), datetime64[ns](1), float64(2), int64(1), object(1) memory usage: 338.0 bytes使用场景:当你已经了解数据概貌,或者在一个需要快速、简洁输出的自动化脚本/日志中,不希望控制台被冗长的列信息刷屏时,这个参数非常有用。
memory_usage参数(可接受布尔值或字符串,默认为None,在多数版本中等同于True): 这个参数控制内存使用情况的显示和计算方式,是进行大数据内存优化的关键入口。memory_usage=True或None: 显示内存使用量(如上例所示)。memory_usage=False: 不显示内存使用量。memory_usage=’deep’:进行深度内存估算。这是最重要的一个选项。默认的内存估算只计算各列本身数据的开销,对于object类型(尤其是字符串)的列,它只计算引用的大小,而不会计算字符串实际内容占用的内存。使用’deep’会进行更精确(但也更耗时)的计算。
# 创建一个包含长字符串的DataFrame df_large_text = pd.DataFrame({'id': range(1000), 'text': ['a_very_long_string_' * 10] * 1000}) print(“默认估算:”) print(df_large_text.info(memory_usage=True)) # 或 None print(“\n深度估算:”) print(df_large_text.info(memory_usage=’deep’))你会看到两个截然不同的
memory usage值。’deep’模式下的值才更接近真实内存消耗。注意:对于大型DataFrame,深度估算可能较慢,建议在需要精确评估内存时使用。show_counts参数(布尔值,默认为None,在多数新版pandas中等同于True): 控制是否显示Non-Null Count。如果数据量极大,计算非空值计数会有开销。当你只关心数据类型和内存,或者明确知道没有缺失值时,可以将其设为False来加速并简化输出。print(df.info(show_counts=False))输出中
Non-Null Count列将消失。null_counts参数(布尔值,旧版参数,已被show_counts取代): 在一些较旧的pandas版本中,你可能看到这个参数,其功能与show_counts相同。在新代码中应使用show_counts。
实操心得:我个人的习惯是,在数据探索的第一步,总是使用df.info()的默认参数,获取最全面的第一印象。如果数据框很大,输出太长,我会先用df.head()看几行数据,再用df.info(verbose=False)看整体类型和内存。当怀疑内存占用异常时,一定会用df.info(memory_usage=’deep’)进行深度检查。
3. 从info()输出中洞察数据问题
info()的输出是一份静态报告,但资深分析师能从中读出动态的“故事”和潜在风险。以下是几种常见的“问题模式”及其诊断思路。
3.1 识别缺失值模式与陷阱
Non-Null Count是发现缺失值最直接的指标。但看绝对值不够,要学会看模式和比例。
- 模式一:整列缺失。如果某一列的
Non-Null Count为0,意味着该列全部是空值。这通常是一个需要删除的列,除非有特殊业务含义。 - 模式二:均匀缺失。多列的非空数量相同,但都小于总行数。这可能意味着这些列的缺失发生在同一批记录上,提示可能存在系统性的数据采集失败(例如,某次问卷中部分题目未被作答)。
- 模式三:单一列大量缺失。例如,总行数100万,某列非空数只有10万。这需要重点审视:这列是否还有保留价值?缺失是随机的还是有规律的(如新上线的功能字段,旧数据没有)?
一个经典陷阱:info()显示的Non-Null Count只识别pandas认可的缺失值,即NaN(对于数值列)或None/NaT(对于对象/时间列)。如果数据中缺失值以其他形式存在,如空字符串“”、“NULL”、“N/A”、-1、999等,info()会将其视为有效值,从而误导你。
注意:在调用
info()之前,一个良好的数据清洗习惯是,先将这些“伪缺失值”统一替换为真正的NaN。例如:df.replace([“”, “NULL”, “N/A”, -1, 999], np.nan, inplace=True)。这样,info()给出的缺失值报告才是真实的。
3.2 诊断数据类型错误
Dtype列是数据质量的另一面镜子。数据类型错误会导致计算错误、性能下降甚至分析逻辑谬误。
- 数值列显示为
object:这是最常见的问题。例如,一个应该是年龄的列,因为混入了“未知”或“twenty-five”这样的字符串,导致整列被推断为object类型。这将导致你无法进行任何数学运算(mean(),sum())。解决方案是使用pd.to_numeric(…, errors=’coerce’)进行强制转换,非法值会变成NaN,此时再结合info()查看新的缺失情况。 - 日期列显示为
object:日期时间数据如果格式不统一,pandas在读取时(如用read_csv)可能无法自动解析,会保留为object。你需要用pd.to_datetime(…, errors=’coerce’)进行转换,同样,转换失败会变成NaT(时间戳类型的缺失值)。 - 分类文本列显示为
object:如果一个列只有有限的几个重复值(如性别:“男”,“女”;城市名),保持为object会浪费大量内存。info()中如果看到大量object类型且内存占用很高,就要考虑是否将其转换为category类型。转换后,info()显示的Dtype会变成category,内存使用量通常会大幅下降。
3.3 评估内存使用与优化方向
memory usage是性能优化的指南针。对于大型数据集,内存直接决定了你能否在本地机器上进行分析,以及计算速度。
- 建立基线:首先用
df.info(memory_usage=’deep’)获取真实内存占用。 - 识别内存大户:
object类型的列通常是内存消耗的主力,尤其是存储了长文本的列。 - 优化策略:
- 向下转换数值类型:
int64可以尝试转为int32或int16,float64可以尝试转为float32。使用df[‘column’].astype(‘int32’)。转换前用df[‘column’].min()和df[‘column’].max()检查值域是否在新类型范围内。 - 使用分类类型:对低基数(唯一值少)的
object列,使用df[‘column’] = df[‘column’].astype(‘category’)。转换后再次运行info(),对比内存节省效果。 - 使用稀疏数据结构:如果数据中绝大部分是相同的值(如0),可以考虑使用稀疏数据类型,但这属于进阶优化。
- 向下转换数值类型:
- 监控优化效果:每进行一次优化操作,就重新运行一次
df.info(memory_usage=’deep’),量化你的优化成果。这是一个非常有效的正反馈循环。
4.info()在数据分析工作流中的实战应用
理解了info()的细节和诊断能力后,我们把它嵌入到一个完整的数据分析工作流中,看看它如何在不同阶段发挥作用。
4.1 数据读取后的首次“体检”
这是info()最标准的应用场景。在pd.read_csv(),pd.read_excel()或从数据库读取数据后,立即执行df.info()。
import pandas as pd # 模拟从CSV读取数据 df_raw = pd.read_csv(‘your_data.csv’) print(“=== 数据首次体检报告 ===”) df_raw.info()这次调用将回答以下问题:
- 数据有多大?(行、列)
- 列名是否符合预期?有没有多余的空格或奇怪的字符?(检查
Column列) - 有没有令人意外的缺失?(检查
Non-Null Count) - 数据类型是否正确?数字、日期是否被正确识别?(检查
Dtype) - 数据规模是否在我的机器内存承受范围内?(检查
memory usage)
基于这份报告,你可以决定下一步是直接开始分析,还是需要先进行数据清洗。
4.2 数据清洗过程中的“监控器”
数据清洗不是一蹴而就的,info()是监控清洗效果的最佳工具。
场景示例:清洗用户信息表
假设原始df_raw的info()显示age列是object类型,且有少量缺失。
# 步骤1:查看原始状态 print(“清洗前:”) df_raw.info() # 步骤2:处理“伪缺失值”和错误格式 df_clean = df_raw.copy() # 将年龄列中的非数字字符(如’约30‘)替换为NaN df_clean[‘age’] = pd.to_numeric(df_clean[‘age’], errors=’coerce’) # 步骤3:查看转换后的效果 print(“\n转换年龄列后:”) df_clean.info() # 此时age列的Dtype应变为float64,Non-Null Count可能减少(非法值变NaN) # 步骤4:处理其他列,如日期列 df_clean[‘join_date’] = pd.to_datetime(df_clean[‘join_date’], errors=’coerce’) # 步骤5:再次查看整体状态 print(“\n清洗完成后:”) df_clean.info()通过对比几次info()的输出,你可以清晰地追踪每一列数据类型的转变和缺失值数量的变化,确保清洗操作按预期进行。
4.3 特征工程与数据合并后的“校验器”
在进行特征衍生、数据合并(merge,concat)或数据透视(pivot)后,数据形状和类型可能发生变化。此时调用info()进行校验至关重要。
# 假设我们有两个数据框要合并 df_users = pd.DataFrame({‘user_id’: [1, 2, 3], ‘name’: [‘A’, ‘B’, ‘C’]}) df_orders = pd.DataFrame({‘order_id’: [‘o1’, ‘o2’, ‘o3’], ‘user_id’: [1, 2, 99], ‘amount’: [100, 200, 300]}) print(“合并前 df_users:”) df_users.info() print(“\n合并前 df_orders:”) df_orders.info() # 进行左连接 df_merged = pd.merge(df_users, df_orders, on=‘user_id’, how=‘left’) print(“\n合并后 df_merged:”) df_merged.info()合并后的info()可以帮你验证:
- 合并后的行数是否符合预期?(左连接应保持左表行数)
- 新增的列(如
order_id,amount)是否成功加入? - 新增列的缺失值情况如何?(例如,
user_id=3的用户没有订单,其order_id和amount应为NaN,这会在Non-Null Count中体现) - 合并键(
user_id)的数据类型在两张表中是否一致?如果不一致(如一个是int64,一个是object),合并可能会失败或产生意外结果,info()可以提前预警。
4.4 自动化脚本与日志记录
在自动化数据管道或定期报告中,将info()的输出记录到日志文件是非常好的实践。你可以使用StringIO来捕获它的输出。
import pandas as pd from io import StringIO import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def process_data(df): “”“模拟数据处理函数”“” # 捕获info输出 buffer = StringIO() df.info(buf=buffer) info_str = buffer.getvalue() # 记录到日志 logger.info(“DataFrame Info after processing:\n%s”, info_str) # 也可以写入文件 with open(‘data_profile.log’, ‘a’) as f: f.write(f”{pd.Timestamp.now()} - DataFrame Info\n”) f.write(info_str) f.write(“\n” + “=”*50 + “\n”) return df # 使用示例 df = pd.DataFrame({‘A’: [1, 2, 3]}) process_data(df)这样,每次脚本运行时,数据的关键状态都会被持久化记录,便于事后审计和问题排查。
5. 常见问题与排查技巧实录
即使对info()很熟悉,在实际使用中还是会遇到一些令人困惑的情况。这里记录了几个我踩过的坑和解决方案。
5.1info()显示内存很小,但实际操作时内存爆炸
问题描述:一个DataFrame调用info()显示内存只有几十MB,但在进行groupby、merge或简单赋值时,Python 进程内存迅速增长到几个GB。
根本原因:info()默认的memory_usage计算方式(非’deep’)严重低估了object类型列的内存占用。object类型在pandas中存储的是 Python 对象的指针(引用),info()只计算了这些指针的大小(通常8字节/值),而没有计算对象本身(如字符串)的大小。当object列存储了大量长字符串时,真实内存消耗可能是指针的数十倍甚至上百倍。
解决方案:永远对包含文本数据的DataFrame使用df.info(memory_usage=’deep’)。这会触发一次全面的内存计算,虽然慢,但能反映真实情况。如果’deep’模式显示内存巨大,那么优化策略就是针对这些object列:删除不必要的文本列、将短文本列转为category、或者考虑使用更高效的数据格式(如parquet)进行存储。
5.2 缺失值数量显示不一致
问题描述:df.info()显示的某列Non-Null Count是 9000,但用df[‘column’].isna().sum()计算出来的缺失值数量却是 9500。
排查思路:这通常是因为数据中存在pandas不认为是NaN的“伪缺失值”。info()只统计真正的NaN/None/NaT。而isna()或isnull()方法也只识别这些真正的缺失值。如果数量不一致,说明你用来计算缺失值的方法可能识别了更多东西(比如,你可能用了df[‘column’].isnull().sum(),这在大多数情况下和isna()等价)。
更可能的情况是:你心里以为的“缺失值”(如空字符串、占位符)并没有被pandas识别。确保在数据清洗的第一步,就使用df.replace()或df.map()将这些伪缺失值统一转换为np.nan。之后,info()和isna().sum()的结果就会一致。
5.3 大数据集下info()执行缓慢或卡住
问题描述:当处理一个有几百万行、上百列的数据集时,调用df.info()需要很长时间,甚至感觉程序无响应。
原因分析:info()为了计算Non-Null Count和内存使用,需要遍历每一列的数据。对于超大数据集,这是一个计算密集型操作。memory_usage=’deep’模式尤其耗时,因为它要深入计算每个对象的大小。
优化策略:
- 采样查看:对于初步探索,不必在全量数据上运行
info()。可以使用df.sample(10000).info()对数据进行采样,获取近似的信息。这能极大提升响应速度。 - 分批查看:如果列非常多,可以使用
df.iloc[:, :20].info()查看前20列的信息,然后再看后面的列。 - 按需使用深度估算:只在怀疑内存有问题时,才使用
memory_usage=’deep’。日常监控用默认模式即可。 - 使用
df.dtypes和df.isna().sum()替代:如果你只关心数据类型和缺失值总数,可以分别调用这两个属性/方法,它们可能比info()更快,因为info()还整合了其他信息。
5.4 如何获取info()的信息以供程序化使用?
问题描述:info()的输出是给人看的字符串,如果想在程序里获取这些数据(比如,自动判断哪些列缺失率超过50%并报警),该怎么办?
解决方案:info()的大部分信息都可以通过DataFrame的其他属性或方法单独获取,然后进行编程处理。
import pandas as pd import numpy as np df = pd.DataFrame({‘A’: [1, 2, np.nan], ‘B’: [‘x’, None, ‘z’]}) # 1. 获取行数和列数 shape = df.shape # (3, 2) # 2. 获取数据类型 dtypes = df.dtypes # 3. 获取每列的非空值数量 non_null_counts = df.count() # 返回一个Series # 4. 获取每列的缺失值数量 null_counts = df.isnull().sum() # 返回一个Series # 5. 计算缺失率 missing_rates = (df.isnull().sum() / len(df)) * 100 # 基于这些信息进行自动化决策 high_missing_cols = missing_rates[missing_rates > 50].index.tolist() if high_missing_cols: print(f“警告:以下列缺失率超过50%,建议检查或删除: {high_missing_cols}”) # 6. 获取内存使用(近似值,非深度) memory_usage = df.memory_usage(deep=False).sum() # 获取深度内存使用(较慢) memory_usage_deep = df.memory_usage(deep=True).sum()通过组合这些属性,你可以构建出比info()更灵活、更强大的自动化数据质量检查脚本。