简介:这份PDF文档围绕Jupyter Notebook中的文件读写与图片处理展开,面向Python数据分析初学者及需要快速上手交互式编程的读者。内容以精简的代码示例贯穿,介绍通过pandas的read_csv读取CSV文件、使用matplotlib的imread和imshow加载并展示图片,同时重点演示如何借助plt.gcf()结合savefig()正确导出非空白的PNG/JPG图片,并补充了如何让Notebook单格输出多个结果的设置方法。资源仅含1个PDF文件,大小约114KB,体量轻巧,适合跟着实例逐行敲写与验证。这份资料已吸引10981人学习,实用性受到广泛认可;读者可从中获得可直接迁移的读写代码模板、避免常见保存空白的排错思路,以及理解Jupyter展示机制的关键技巧,对日常数据处理与可视化工作有直接帮助。
1. 别急着点保存:Jupyter 里读文件和存图片的坑
用 Jupyter Notebook 做数据分析时,读文件和存图片的频率比很多人想象得高。你可能已经习惯用pd.read_csv()读表格、用plt.imshow()显示图片,但真正到保存结果这一步,不少人会踩到一个反直觉的坑:直接调用plt.savefig('./66.png'),生成的图片文件打开后是空白的。原因不在保存逻辑,而在于 Jupyter 对 matplotlib 图形对象的生命周期管理和普通 Python 脚本不一样。这篇文章围绕 Jupyter 里“读文件、读图片、导出文件、导出图片”这条链路,把每一环节的 API 选择、参数含义和状态机制拆开讲清楚,并给出可直接复制的代码。适合数据分析和机器学习从业者,尤其是经常在 Notebook 里做可视化、需要把中间结果落盘的人。
2. 用 pandas 读文件:read_csv 的正确打开方式
2.1 从 read_csv 说起:最小可运行代码
先看最基础的用法。在 Jupyter 单元格里执行下面的代码,会把当前工作目录下的test.csv读入内存,生成一个DataFrame:
import pandas as pd data = pd.read_csv('./test.csv') data.head()read_csv()的返回类型是pandas.core.frame.DataFrame。head()的作用是显示前 5 行,方便在 Notebook 里快速预览数据形态。这里有一个容易忽略的点:read_csv()默认把第一行当作列名,如果你的 CSV 没有表头,要显式传入header=None,否则第一行数据会被当成字段名,后续分析全部错位。
2.2 参数表:哪些参数在实战中必须关注
read_csv()参数有几十个,但实际项目中高频使用的就那么几个。下面这张表列出的都是我在真实数据文件上验证过的配置项,按使用频率排序:
| 参数 | 默认值 | 作用 | 典型场景 |
|---|---|---|---|
filepath_or_buffer | 无 | 文件路径或 URL | 本地相对路径、绝对路径、HTTP 链接 |
sep | , | 分隔符 | 制表符用\t,分号用; |
header | infer | 指定表头行 | 无表头时设None |
encoding | utf-8 | 文件编码 | Windows 下常见gbk/gb2312 |
dtype | None | 指定列数据类型 | 防止 ID 列被读成数值 |
parse_dates | False | 自动解析日期列 | 时间序列分析 |
usecols | None | 只读指定列 | 大文件减少内存占用 |
nrows | None | 只读前 N 行 | 快速探查数据 |
补充一个常见的坑:读含中文的 CSV 文件时,如果直接pd.read_csv报UnicodeDecodeError,优先试encoding='gbk'。这不只是 Windows 的问题,从 Excel 导出的 CSV 在 macOS 上也可能保存为gbk或gb18030编码,报错后不要急着改数据,先换编码参数。
2.3 大文件读取与内存控制
对于几百 MB 甚至上 GB 的 CSV,直接read_csv()会把整个文件加载进内存,容易把 Notebook 内核撑爆。常见的做法是分块读取,配合usecols只加载需要的列:
chunk_iter = pd.read_csv( './large_data.csv', usecols=['user_id', 'click_time', 'item_id'], dtype={'user_id': 'int32', 'item_id': 'int32'}, chunksize=50000 ) result = [] for chunk in chunk_iter: # 按块做聚合,再合并结果 result.append(chunk.groupby('user_id')['click_time'].count()) summary = pd.concat(result).groupby(level=0).sum()这里chunksize=50000表示每次迭代只处理 5 万行,内存占用被控制在一个稳定值,处理完一个 chunk 后内存会被回收。dtype显式指定为int32,相比默认的int64能省一半内存。需要注意,分块聚合后要用groupby(level=0).sum()再做一次汇总,因为同一个user_id可能分散在不同 chunk 里。
提示:Jupyter 单元格执行后没有任何反应,先检查是不是文件路径写错了。
read_csv('./test.csv')中的./是相对当前工作目录的路径。在 Notebook 里用!pwd查看当前工作目录,!ls查看目录下文件,比反复猜测路径更高效。
3. 读取图片:imread 与 imshow 的分工
3.1 imread 返回的是什么
matplotlib.pyplot.imread()用于读取图片文件,返回值是一个 NumPy 数组。对于一张彩色图片,数组的 shape 是(高度, 宽度, 通道数),数据类型通常是uint8(取值范围 0 到 255)。对于灰度图,shape 是(高度, 宽度),没有通道维度。下面用代码验证:
import matplotlib.pyplot as plt img = plt.imread('./d.jpg') print(type(img)) # <class 'numpy.ndarray'> print(img.shape) # 例如 (1080, 1920, 3) print(img.dtype) # uint8这里imread()只做“读取”,并不负责“显示”。如果你只执行上面的代码,Notebook 单元格里只会显示ndarray的类型信息,不会弹出图片。要看到图片内容,必须用plt.imshow(img)。
3.2 显示图片与图像数据的关系
plt.imshow(img) plt.axis('off') # 关闭坐标轴,否则会显示 x/y 轴刻度 plt.show()imshow()接收的数组必须是合法的图像数据。如果用plt.imread()读取,数据类型天然合法。但如果你自己用 NumPy 生成数组再显示,要注意两个细节:第一,dtype必须是uint8或float(范围 0 到 1),否则颜色会异常;第二,RGB 三通道的顺序不能乱,matplotlib要求通道顺序是 R(红)、G(绿)、B(蓝),如果你用 OpenCV 读取图片,得到的是 BGR 顺序,直接imshow()会导致红蓝通道互换。
3.3 生成测试图:绕开外部文件依赖
有时候你手头没有现成的图片文件,但又想测试图像处理流程。一个可靠的方式是用np.random.randint()生成一张噪声图:
import numpy as np s = np.random.randint(0, 255, (300, 400, 3), dtype=np.uint8) img_shape = s.shape print(f"生成的图像尺寸: {img_shape[1]} x {img_shape[0]}, 通道数: {img_shape[2]}")这里的关键参数是(300, 400, 3):300 是高度,400 是宽度,3 是 RGB 通道数。dtype=np.uint8把灰度范围限制在 0-255,这样imshow()不需要额外做数据转换。很多图像处理任务想快速验证算法逻辑,用这种噪声图比反复找真实图片更高效,因为像素值完全可控。
注意:
np.random.randint生成的是随机噪声,不是自然图像。如果你拿它测试边缘检测、目标识别等算法,结果没有参考意义,只适合验证“数据格式是否正确 + 显示流程是否跑通”。
4. 导出图片:savefig 空白之谜与 gcf() 解决方案
4.1 为什么直接 plt.savefig 会生成空白图
这是整篇文章的核心问题。下面的代码几乎所有初学者都写过:
plt.imshow(s) plt.savefig('./save.png') # 生成的文件是空白的现象是:Notebook 里图片显示正常,但保存到磁盘的save.png打开后是纯白或纯透明。原因在于 matplotlib 的绘图状态机制:plt.savefig()保存的是“当前 figure 对象”,但在 Jupyter 中,如果使用了%matplotlib inline,Notebook 的显示机制会在单元格结束时自动关闭当前 figure,并把画布内容渲染到输出区域。当你调用plt.savefig()时,虽然执行顺序在显示之前,但实际的 figure 状态可能已经被 Jupyter 的后端干扰了。
这个问题的本质是:plt.savefig()是 pyplot 接口的一个快捷方式,它内部隐含获取“当前 figure”。在 Jupyter 的交互式环境下,“当前 figure”的指向并不总是你刚画完的那一个。解决思路是显式地拿到 figure 对象的引用,再调用该对象的savefig()方法。
4.2 用 gcf() 获取当前图形对象
import matplotlib.pyplot as plt import numpy as np # 生成一张测试图 s = np.random.randint(0, 255, (300, 400, 3), dtype=np.uint8) # gcf: Get Current Figure,获取当前图形对象 fig = plt.gcf() plt.imshow(s) plt.axis('off') # 调用 figure 对象的 savefig 方法 fig.savefig('./output.png', dpi=150, bbox_inches='tight')执行这段代码后会生成一个非空白的output.png。核心是plt.gcf()这一行:它返回 matplotlib 内部维护的“当前 figure”对象,后续的fig.savefig()保存的就是这个对象。相比plt.savefig(),这种方式明确指定了操作对象,绕开了 Jupyter 对 pyplot 全局状态的干扰。
dpi=150控制输出分辨率,数值越大图片越清晰,但文件体积也越大。bbox_inches='tight'是另一个常用参数,它会自动裁剪图片周围的空白区域,保证保存的内容紧贴图像边界。如果不设置这个参数,保存的图片四周会有大块白边。
4.3 保存参数对比表
| 参数 | 默认值 | 作用 | 建议 |
|---|---|---|---|
dpi | figure.dpi(默认 100) | 输出分辨率 | 印刷需求设 300,屏幕展示 150 足够 |
bbox_inches | None | 是否裁剪空白区域 | 常用'tight',所见即所得 |
transparent | False | 是否透明背景 | 在网页嵌入时用到 |
format | 由扩展名推断 | 强制保存格式 | 'png'、'jpg'、'pdf'、'svg' |
facecolor | 'white' | 画布背景色 | 设'none'可配合透明背景 |
一个容易被忽略的点是:fig.savefig()支持的文件格式取决于文件扩展名。你写成./output.png就保存为 PNG,写成./output.jpg就保存为 JPEG。Jupyter 场景下 PNG 是首选,因为 JPEG 不支持透明通道,且会有压缩痕迹。如果你要插入 LaTeX 或 Word 文档,保存为 PDF 或 SVG 是矢量格式,放大不失真。
4.4 在绘图场景中的 gcf() 用法
plt.imshow()只是显示图片,更常见的组合是先画一条曲线,再保存。比如画一个折线图:
import numpy as np import matplotlib.pyplot as plt x = np.linspace(0, 10, 100) y = np.sin(x) fig = plt.gcf() # 先获取 figure,再画图 plt.plot(x, y, label='sin(x)') plt.xlabel('x') plt.ylabel('y') plt.legend() fig.savefig('./curve.png', dpi=200, bbox_inches='tight')注意这里获取fig的时机放在plt.plot()之前。原因在于plt.gcf()返回的永远是当前活跃的 figure,如果你的 Notebook 之前已经画过图,且没有关闭 figure,gcf()可能返回的是旧的图形对象,导致新画的曲线被加到了旧 figure 上。先获取再绘图,可以保证fig和后面的绘图操作属于同一个对象。
4.5 批量导出多张图片时的对象隔离
实际项目中经常要在循环里生成多张图表。如果每次都调plt.gcf(),会发现所有图片都保存在同一个 figure 上。正确的做法是在循环里显式创建新 figure:
import os os.makedirs('./figures', exist_ok=True) for i in range(3): fig = plt.figure(figsize=(6, 4)) data = np.random.randn(100) plt.plot(np.cumsum(data), label=f'run-{i}') plt.legend() fig.savefig(f'./figures/plot_{i}.png', dpi=150) plt.close(fig) # 显式关闭,释放内存plt.figure(figsize=(6, 4))创建了一个独立的新 figure,figsize控制图片尺寸(英寸)。循环结束后用plt.close(fig)关闭当前 figure,这一步很重要,否则内存会被持续占用,Notebook 长时间运行会越来越卡。os.makedirs('./figures', exist_ok=True)的作用是自动创建输出目录,避免因为目录不存在而保存失败。
提示:如果保存路径中包含中文或特殊字符,建议在代码文件头部加
# -*- coding: utf-8 -*-,某些系统下savefig会因为路径编码问题抛出OSError。
5. 让 Notebook 输出多个结果:ast_node_interactivity 与图片处理扩展
5.1 默认行为:只显示最后一个表达式
Jupyter Notebook 的默认配置是每个单元格只显示最后一个表达式的返回值。比如下面的代码:
1 + 1 2 + 2 3 + 3执行后只会在单元格下方显示6,前面的2和4不会显示。这在数据探索阶段很烦人,因为你可能想同时看到多个变量的值,被迫用print()逐行输出,代码显得很冗余。
5.2 InteractiveShell 的分行显示设置
通过修改 IPython 的交互模式,可以让所有表达式的输出都显示出来:
from IPython.core.interactiveshell import InteractiveShell InteractiveShell.ast_node_interactivity = 'all'ast_node_interactivity是 IPython 的一个配置项,默认值是'last_expr'(只显示最后一个表达式),改为'all'后,单元格内所有表达式的返回值都会被渲染。设置后,下面这段代码会同时显示三个数字:
1 + 1 2 + 2 3 + 3输出区域会依次显示2、4、6。这对数据探索、模型评估时查看多个指标非常有用。注意这个设置只对当前 Notebook 会话生效,重启内核后需要重新执行一次。如果你希望所有 Notebook 都默认开启,需要修改 IPython 配置文件,但在团队协作场景下不建议这么改,因为不同成员的配置不一致会导致“别人跑不出来同样的结果”。
5.3 多张图片同时显示的实现方式
ast_node_interactivity='all'对图片同样有效。执行下面的代码,单元格下方会一次性显示三张图:
import matplotlib.pyplot as plt import numpy as np fig1, ax1 = plt.subplots() ax1.imshow(np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)) fig2, ax2 = plt.subplots() ax2.imshow(np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)) fig3, ax3 = plt.subplots() ax3.imshow(np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8))这里用了plt.subplots(),它返回一个元组(fig, ax),其中fig是 Figure 对象,ax是 Axes 对象(坐标轴区域)。如果不用plt.subplots()而是直接plt.figure(),在同一个单元格里画多张图,输出区域通常只会显示最后一个 figure。subplots()显式创建了三个独立的fig,配合ast_node_interactivity='all',三个 figure 都会作为独立的输出对象显示。
5.4 实际项目中的落地技巧
结合前四章的内容,一个完整的“读文件 → 处理 → 导出图片 → 多结果展示”流程可以这样组织:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from IPython.core.interactiveshell import InteractiveShell InteractiveShell.ast_node_interactivity = 'all' # 1. 读取数据 df = pd.read_csv('./test.csv', encoding='utf-8') # 2. 提取一列作为纵轴数据,构造横轴索引 y = df['value'].to_numpy() x = np.arange(len(y)) # 3. 画图并保存 fig = plt.gcf() plt.plot(x, y) plt.xlabel('index') plt.ylabel('value') plt.title('data overview') fig.savefig('./data_overview.png', dpi=150, bbox_inches='tight') # 4. 同时输出数据摘要和图片,一次看全 print(f"数据行数: {len(df)}") print(f"缺失值统计:\n{df.isnull().sum()}") df.describe()这个单元格把前三章的技能串成了完整的分析链路。df['value'].to_numpy()将 pandas 的 Series 转成 NumPy 数组,转成 ndarray 后可以配合np.arange生成横轴索引。df.describe()在设置了ast_node_interactivity='all'后会直接显示统计结果,不需要额外加print()。保存图片的代码被放在describe()之前,确保fig对象在绘图后立即落盘,避免最后单元格里的其他输出干扰 matplotlib 的图形状态。
如果图片保存后仍然空白,可以用下面的代码做一次快速诊断:
import os file_path = './data_overview.png' print(f"文件是否存在: {os.path.exists(file_path)}") print(f"文件大小: {os.path.getsize(file_path)} 字节")文件存在但大小为 0 字节,说明savefig()没有正确写入数据,优先检查fig = plt.gcf()是否放在了绘图命令之前。文件完全不存在,检查输出目录是否有写权限,用os.makedirs('./output', exist_ok=True)创建目录后再保存。
本文还有配套的精品资源,点击获取