1. 项目概述:为什么说 Pandas 是数据工作者的“瑞士军刀”?
我带过不少刚转行进数据分析和机器学习领域的新人,也给企业客户做过十几期内部培训。每次讲到工具链选型,总有人问:“Python 有那么多库,NumPy、SciPy、Scikit-learn 各司其职,为什么 Pandas 还要单独学?它到底不可替代在哪?”——这个问题,我十年前第一次用
.read_csv()
读进一个 200MB 的销售日志时就问过自己。今天回看,答案很朴素:
Pandas 不是另一个计算库,而是你和数据之间最自然、最符合人类直觉的对话界面。
它把“我要看前三行”“这个字段里有多少空值”“把销售额按城市分组求平均”这些日常思考,直接翻译成一行可执行、可复现、可协作的代码。关键词“Data Manipulation”不是虚词——它意味着清洗、筛选、聚合、对齐、重塑、采样、标注……所有让原始数据变成可用信息的动作,Pandas 都提供了语义清晰、性能扎实、错误提示友好的原生支持。
它不像 SQL 那样需要你先建表、定义 schema、写 JOIN;也不像 Excel 那样拖拽几下就卡死、公式一复制就出错、版本一更新就格式全乱。Pandas 的 Series 和 DataFrame,本质上是带标签的 NumPy 数组,但多了索引(index)、列名(columns)、缺失值语义(NaN)、自动对齐(alignment)和内置统计方法——这四点,就是它碾压其他工具的核心护城河。比如你用
df['A'] + df['B']
,Pandas 会自动按 index 对齐相加,哪怕两列长度不同、顺序打乱、中间有缺失,它也能给出合理结果;而 NumPy 直接报错,Excel 手动对齐能让你怀疑人生。再比如
.corr()
返回的不是单个数字,而是一个完整的相关系数矩阵,行列都带着变量名,你一眼就能看出哪两个特征强相关——这种“所思即所得”的体验,是工程效率的质变。我经手过的项目里,85% 的数据预处理时间花在 Pandas 上,剩下 15% 才交给模型训练。这不是巧合,是它真正解决了数据科学流水线中最琐碎、最耗神、最容易出错的那部分。所以这篇内容不叫“Pandas 入门教程”,它是一份我十年实战中反复验证、不断精简、最终沉淀下来的“Pandas 核心能力地图”。没有废话,不讲历史,只聚焦那些你每天都会用、用错就会踩坑、用熟就能提速的关键操作。接下来的内容,全部基于真实项目场景展开,包括 landslide 预测数据集的完整分析链路,每一步都有原理说明、参数依据和避坑提示。
2. 核心数据结构解析:Series 与 DataFrame 的本质差异与协同逻辑
2.1 Series:不只是“一列数据”,而是带坐标系的标量序列
很多人初学时把 Series 理解为“DataFrame 的一列”,这没错,但太浅。Series 的本质,是 一个一维的、带显式索引(index)的、类型感知(dtype-aware)的数组 。它的三个核心组件是:values(数据值)、index(索引标签)、dtype(数据类型)。这三者缺一不可,共同决定了 Series 的行为边界。
举个例子,假设你有一组传感器读数:
[23.4, 24.1, 22.9, 25.0]
。如果用纯 Python list 存,它只是四个数字;用 NumPy array 存,它是一段连续内存里的浮点数;但用 Series 存,它立刻拥有了“身份”:
import pandas as pd
import numpy as np
# 模拟传感器时间戳和读数
timestamps = ['2023-07-01 08:00', '2023-07-01 09:00', '2023-07-01 10:00', '2023-07-01 11:00']
readings = [23.4, 24.1, 22.9, 25.0]
s = pd.Series(readings, index=timestamps, name="Temperature_C")
print(s)
输出:
2023-07-01 08:00 23.4
2023-07-01 09:00 24.1
2023-07-01 10:00 22.9
2023-07-01 11:00 25.0
Name: Temperature_C, dtype: float64
注意这里的关键点:
-
索引不是行号
:
s[0]返回的是第一个元素23.4,但s['2023-07-01 08:00']也返回23.4。这意味着你可以用标签(label)或位置(position)两种方式访问,但语义完全不同。.iloc[0]强制按位置取,.loc['2023-07-01 08:00']强制按标签取。如果索引是日期,.loc还支持切片:s.loc['2023-07-01 08:00':'2023-07-01 10:00']。 -
dtype 决定运算规则
:
s.dtype是float64,所以s * 1.8 + 32能正确做摄氏转华氏。但如果s是字符串 Series(如['a', 'b', 'c']),s + 'x'就是拼接,而不是报错。Pandas 会根据 dtype 自动选择运算符重载逻辑。 -
缺失值有明确定义
:
s.iloc[2] = np.nan后,s.count()返回3(忽略 NaN),s.size返回4(包含 NaN)。这是业务逻辑的关键——统计“有效观测数”和“总记录数”是两回事,Pandas 用两个方法明确区分,避免 Excel 里用COUNT和COUNTA混淆的悲剧。
提示:Series 的
.name属性常被忽略,但它在合并(concat)、分组(groupby)时至关重要。比如pd.concat([s1, s2], axis=1)会用.name作为列名。不设.name,列名就变成0, 1,后续维护成本陡增。
2.2 DataFrame:二维表格的“活体模型”,而非静态快照
DataFrame 是 Series 的容器,但绝非简单堆叠。它的设计哲学是:
让二维数据拥有和一维数据同等的表达自由度与操作便利性。
一个 DataFrame 由
index
(行索引)、
columns
(列索引)、
values
(二维数组)和
dtypes
(每列 dtype)构成。关键在于,它的
index
和
columns
都是可独立操作的 Series,这意味着你可以对行、对列、对单元格进行任意维度的切片、过滤、广播。
回到 landslide 数据集。
df = pd.read_csv("landslide.csv")
加载后,
df.shape
是
(1000, 10)
,表示 1000 行、10 列。但
df.columns
不是简单的字符串列表,而是一个 Index 对象:
print(df.columns)
# Index(['Aspect', 'Curvature', 'Earthquake', 'Lithology', 'Slope', 'Soil',
# 'TRI', 'TWI', 'Landuse', 'Landslide'], dtype='object')
这个 Index 支持向量化操作。比如你想快速找出所有数值型列(排除 'Lithology' 和 'Landuse' 这类分类变量),不用写循环:
numeric_cols = df.select_dtypes(include=[np.number]).columns
# 或更精确地:df.columns[df.dtypes != 'object']
而
df.index
默认是
RangeIndex(start=0, stop=1000, step=1)
,但你可以随时替换为更有意义的索引:
# 假设数据有唯一ID列,把它设为索引
df = df.set_index('ID') # 现在 df.loc[123] 就能直接取第123号样本
# 或者用多级索引处理时空数据
df_multi = df.set_index(['Region', 'Date']) # 支持 df_multi.loc[('North', '2023-01-01')]
注意:
set_index()默认会删除原列,加drop=False可保留。这是新手常踩的坑——设完索引发现原列没了,还得reset_index()回来,白白浪费时间。
DataFrame 的强大,在于它把“行操作”和“列操作”完全对称化。
df['Aspect']
返回 Series,
df.iloc[:, 0]
也返回 Series,但前者是按列名(label)取,后者是按位置(position)取。而
df.loc[:, 'Aspect']
和
df.iloc[:, 0]
在效果上等价,但语义不同:前者强调“我要 Aspect 这一列的所有行”,后者强调“我要第一列的所有行”。在数据探索阶段,
.loc
更安全,因为列名不会因 CSV 导入顺序变化而错位;在性能敏感的批处理中,
.iloc
更快,因为它跳过了标签查找。
2.3 Series 与 DataFrame 的共生关系:自动对齐是灵魂所在
Pandas 最反直觉也最强大的特性,是 自动索引对齐(Automatic Index Alignment) 。当你对两个 Series 或两个 DataFrame 做算术运算时,Pandas 不是简单地按位置逐元素计算,而是先将它们的索引对齐,再在对齐后的交集上运算,缺失位置补 NaN。
看这个经典例子:
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([4, 5, 6], index=['b', 'c', 'd'])
print(s1 + s2)
# a NaN
# b 6.0
# c 9.0
# d NaN
# dtype: float64
结果不是
[1+4, 2+5, 3+6] = [5,7,9]
,而是先对齐索引
{'a','b','c'}
和
{'b','c','d'}
,交集是
{'b','c'}
,所以只有
b
和
c
有值,
a
和
d
因为在对方索引中不存在,结果为 NaN。这个机制在真实项目中救了我无数次。比如,你有两个数据源:一个是每日销售汇总(索引是日期),另一个是每周库存快照(索引也是日期)。用
sales_df - inventory_df
,Pandas 会自动只计算那些“既有销售又有库存”的日期,其他日期直接标为 NaN,提醒你数据不匹配——这比手动
merge
后再计算,少写十行代码,且逻辑更健壮。
DataFrame 间的运算同理。
df1 + df2
会先对齐
index
和
columns
,只在两者都存在的行列交叉点上计算。这使得特征工程变得极其简洁:比如你想计算“坡度与曲率的比值”,直接
df['Slope'] / df['Curvature']
即可,无需担心两列长度是否一致、顺序是否相同。Pandas 会用它们的
index
自动对齐。这才是“Data Manipulation”真正的生产力内核:它把开发者从底层的内存管理和索引协调中解放出来,让你专注在业务逻辑本身。
3. 核心操作实操详解:从加载到洞察的完整链路
3.1 数据加载与初步探查:
.read_csv()
的隐藏参数与陷阱
pd.read_csv()
看似简单,却是整个分析链路的“第一道闸门”。90% 的后续问题,根源都在这里没设对参数。以 landslide.csv 为例,原始文件可能有以下典型问题:
-
编码问题
:Windows 生成的 CSV 常用
gbk或cp1252,Linux/macOS 默认utf-8。用错编码会显示乱码或报错UnicodeDecodeError。 -
分隔符非逗号
:有些数据导出用
;或\t(制表符)。 - 首行非列名 :可能有标题行、注释行或空行。
-
缺失值标记不统一
:除了
NaN,还可能有'NULL','N/A','?', 空字符串''。 -
列类型推断错误
:比如
Lithology是分类变量,但 Pandas 推断为object,后续做groupby会慢;Earthquake是布尔型(0/1),却被当int64处理。
正确的加载姿势是:
# 1. 先用小样本探测结构
sample = pd.read_csv("landslide.csv", nrows=5) # 只读前5行
print(sample)
print(sample.dtypes)
# 2. 根据探测结果,精准配置参数
df = pd.read_csv(
"landslide.csv",
encoding='utf-8', # 显式指定,避免依赖系统默认
sep=',', # 显式指定分隔符,即使默认也写上,提高可读性
skiprows=0, # 跳过前n行,如有注释行则设为1
na_values=['NULL', 'N/A', '?', ''], # 显式声明哪些字符串算缺失值
dtype={
'Lithology': 'category', # 强制设为category,节省内存,加速groupby
'Landuse': 'category',
'Earthquake': 'boolean', # pandas 1.5+ 支持boolean类型,自动处理True/False/NaN
'Landslide': 'boolean'
},
parse_dates=None, # 如有日期列,如['Date'],在此指定,自动转为datetime64
low_memory=False # 关键!防止混合类型列被chunk读取导致dtype推断不一致
)
实操心得:永远不要省略
nrows=5的探测步骤。我见过太多人直接read_csv大文件,结果因为某列有异常值(如本该是数字的列混入了字符串),Pandas 报DtypeWarning,然后整列被设为object,后续所有数值计算都失效。用小样本快速确认结构,5秒的事,能省去后面2小时 debug。
加载后,
.head()
和
.tail()
是必做动作,但更要紧的是
.info()
和
.describe()
:
df.info() # 查看每列非空值数量、内存占用、dtype
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 1000 entries, 0 to 999
# Data columns (total 10 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 Aspect 1000 non-null float64
# 1 Curvature 1000 non-null float64
# 2 Earthquake 998 non-null boolean # 注意:这里只有998个非空,说明有2个缺失
# ...
# memory usage: 78.2 KB
df.describe(include='all') # include='all' 会同时显示数值列和分类列的统计
.describe()
对数值列输出
count
,
mean
,
std
,
min
,
25%
,
50%
,
75%
,
max
;对分类列(
include='object'
或
include='category'
)则输出
count
,
unique
,
top
(最频繁值),
freq
(频次)。这能瞬间告诉你:
Lithology
有 5 个唯一值,
Landuse
有 8 个,
Landslide
的
top
是
False
,说明负样本(无滑坡)占多数——这是建模前必须掌握的先验知识。
3.2 数据清洗:处理缺失、重复与异常值的工业级流程
清洗不是“删掉 NaN 就完事”,而是一套有逻辑、可追溯、可复现的决策流。针对 landslide 数据,我们按优先级处理:
3.2.1 缺失值(Missing Values)
df.isna().sum()
给出每列缺失总数。
Earthquake
有 2 个缺失,
Lithology
有 5 个。处理策略取决于缺失机制:
-
随机缺失(MAR/MCAR)
:可以用均值/中位数/众数填充。
Lithology是分类变量,用众数(mode):litho_mode = df['Lithology'].mode()[0] # mode() 返回Series,取第一个值 df['Lithology'].fillna(litho_mode, inplace=True) -
结构性缺失(MNAR)
:比如
Earthquake缺失,可能意味着该区域从未发生过地震,应填False。这需要领域知识,不能盲目填充。我的做法是:先df[df['Earthquake'].isna()]查看这些样本的其他特征(如Slope,TRI),如果它们都集中在某个地理区域,就咨询地质专家确认填充逻辑。
注意:
inplace=True虽然方便,但不利于调试。推荐链式写法:df = df.fillna({'Lithology': litho_mode, 'Earthquake': False}),这样每一步都清晰可逆。
3.2.2 重复值(Duplicates)
df.duplicated().sum()
查重。如果有,用
df.drop_duplicates(keep='first')
删除,但务必先检查:
df[df.duplicated(keep=False)]
查看所有重复行,确认它们确实是脏数据,而非真实重复观测(比如同一地点多次测量)。我曾在一个气象项目中,发现
duplicated()
返回 120 行,但人工核查发现是同一站点在同一天的多次校准读数,应该保留,只是要加个
Measurement_ID
列区分。
3.2.3 异常值(Outliers)
对数值列,用 IQR(四分位距)法比单纯看
min/max
更鲁棒:
def detect_outliers_iqr(series, multiplier=1.5):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - multiplier * IQR
upper_bound = Q3 + multiplier * IQR
return ((series < lower_bound) | (series > upper_bound))
# 应用到所有数值列
numeric_cols = df.select_dtypes(include=[np.number]).columns
outlier_mask = pd.DataFrame()
for col in numeric_cols:
outlier_mask[col] = detect_outliers_iqr(df[col])
# 查看哪些行有异常值(任一列异常即标为True)
df['has_outlier'] = outlier_mask.any(axis=1)
print(f"Total outliers: {df['has_outlier'].sum()} rows")
# 分析异常行的 Landslide 分布
print(df[df['has_outlier']]['Landslide'].value_counts())
如果异常值集中在
Landslide=True
样本中,可能是真实极端事件,不应删除;如果均匀分布,则考虑 winsorize(缩尾)或删除。
df[col] = df[col].clip(lower=lower_bound, upper=upper_bound)
是安全的缩尾操作。
3.3 数据探索与可视化:用
.plot
快速构建数据直觉
Pandas 内置的
.plot
是探索性数据分析(EDA)的利器,它调用 Matplotlib,但语法极简。关键是要理解每种图解决什么问题:
-
直方图(
.hist()) :看单变量分布形状、偏态、峰态。df['Aspect'].plot.hist(bins=30, alpha=0.7)。bins=30避免条形过粗丢失细节;alpha=0.7便于叠加多图。 -
箱线图(
.boxplot()) :看分布中心、离散度、异常值。df.boxplot(column=['Slope', 'TRI', 'TWI'], by='Landslide'),按滑坡与否分组对比,一眼看出Slope在正样本中更高。 -
散点图(
.scatter()) :看两变量关系。df.plot.scatter(x='Slope', y='Curvature', c='Landslide', cmap='coolwarm'),用颜色映射目标变量,直观发现聚类。 -
密度图(
.plot.kde()) :平滑版直方图,适合比较分布。df[df['Landslide']==True]['Slope'].plot.kde(label='Landslide=True'); df[df['Landslide']==False]['Slope'].plot.kde(label='Landslide=False')。
实操心得:别一上来就画一堆图。我的 EDA 流程是:1) 先用
df.corr()看数值变量间线性相关性,揪出高度相关的(|r|>0.8),比如TRI和TWI常高度相关,后续建模要考虑共线性;2) 对高相关变量对,画散点图加回归线;3) 对目标变量Landslide,分别画各特征的条件分布(如df.boxplot(column='Slope', by='Landslide'))。这样有重点,不盲画。
.corr()
返回的 Pearson 相关系数,只衡量线性关系。对于非线性关系(如
Slope
和
Landslide
可能是 U 型),要用
.corr(method='spearman')
(Spearman 秩相关)或画
sns.jointplot()
。Pandas 本身不提供,但
df['Slope'].rank().corr(df['Landslide'].rank())
就是 Spearman。
3.4 特征工程:从原始字段到模型就绪的转换艺术
特征工程是 Pandas 发挥最大价值的战场。它不是“加几个新列”,而是基于业务逻辑的数据变形。针对 landslide 数据,我们做三类典型操作:
3.4.1 数值特征缩放与变换
所有特征被归一化到 1-5,但
Earthquake
和
Lithology
是例外。
Earthquake
是布尔型,直接
df['Earthquake'] = df['Earthquake'].astype(int)
转为 0/1 即可。
Lithology
是分类变量,不能直接用数字编码(
'Granite'=1, 'Sandstone'=2
会引入虚假序关系),要用 one-hot:
# pandas get_dummies 是最安全的one-hot方式
litho_dummies = pd.get_dummies(df['Lithology'], prefix='Litho')
df = pd.concat([df, litho_dummies], axis=1)
df.drop('Lithology', axis=1, inplace=True) # 删除原列
prefix='Litho'
确保新列名为
Litho_Granite
,
Litho_Sandstone
,避免命名冲突。
get_dummies
自动处理缺失值(生成一列
Litho_nan
),如果你不想保留,加
drop_first=True
删除第一列(避免虚拟变量陷阱)。
3.4.2 时间/空间特征构造
虽然本数据集无时间列,但假设有
Date
列,常用操作:
# 假设 df['Date'] 是 datetime 类型
df['Year'] = df['Date'].dt.year
df['Month'] = df['Date'].dt.month
df['DayOfWeek'] = df['Date'].dt.dayofweek # Monday=0, Sunday=6
df['IsWeekend'] = df['DayOfWeek'].isin([5,6])
.dt
访问器是 Pandas 的魔法,它把 datetime Series 当作一个结构体,
year
,
month
等属性直接返回 int Series,无需
apply(lambda x: x.year)
。
3.4.3 交互特征(Interaction Features)
领域知识驱动的组合。地质学家常说:“滑坡风险 = 坡度 × 地震活动 × 土壤类型”。我们可以构造:
# 坡度与地震的交互(线性)
df['Slope_X_Earthquake'] = df['Slope'] * df['Earthquake'].astype(int)
# 坡度与岩性的交互(用 .map 映射权重)
litho_weight = {'Granite': 0.2, 'Sandstone': 0.5, 'Clay': 0.8, 'Loam': 0.4, 'Silt': 0.6}
df['Slope_X_LithoWeight'] = df['Slope'] * df['Lithology'].map(litho_weight)
.map()
是处理分类变量映射的黄金函数,比
replace()
更灵活,比
apply()
更快。
4. 高阶技巧与常见问题排查:那些文档里不写的实战经验
4.1 性能优化:当 DataFrame 变大时,如何不卡死?
Pandas 在 1GB 以内数据上飞快,但超过 5GB 就开始吃力。我的优化清单:
-
用
category替代object:df['Lithology'].nbytes可能是 800KB,df['Lithology'].astype('category').nbytes可能只有 100KB。内存降 80%,groupby速度升 3 倍。 -
用
pd.eval()替代普通运算 :df['new_col'] = df['A'] + df['B'] * df['C']是逐列计算;df['new_col'] = pd.eval('A + B * C')用 numexpr 引擎,内存更优,速度更快,尤其对大数组。 -
用
query()替代布尔索引 :df[df['Slope']>30]创建临时布尔数组;df.query('Slope > 30')直接解析字符串,内存更友好,且支持@variable引用外部变量:threshold = 30; df.query('Slope > @threshold')。 -
分块读取(Chunking)
:处理超大 CSV 时:
chunks = [] for chunk in pd.read_csv("huge_file.csv", chunksize=10000): processed_chunk = chunk.pipe(clean_function).pipe(feature_engineer) chunks.append(processed_chunk) df_final = pd.concat(chunks, ignore_index=True)
4.2 常见报错与速查解决方案
| 报错信息 | 根本原因 | 解决方案 |
|---|---|---|
KeyError: 'Column_Name'
| 列名有空格或大小写不一致 |
df.columns = df.columns.str.strip().str.lower()
清洗列名;用
df.columns.tolist()
查看真实列名
|
SettingWithCopyWarning
|
对
df[condition]
的结果赋值,Pandas 不确定是原视图还是副本
|
用
.loc
显式定位:
df.loc[df['Slope']>30, 'Risk'] = 'High'
;或用
copy()
明确创建副本
|
ValueError: cannot convert float NaN to integer
|
对含 NaN 的列强制
astype(int)
|
先
fillna(-1)
,再
astype('Int64')
(pandas 的可空整型)
|
MemoryError
| DataFrame 太大,超出 RAM |
启用
dtype
精确指定(如
int32
代替
int64
);用
category
;或改用 Dask(Pandas 的并行扩展)
|
4.3 那些“看似简单”却极易出错的操作
-
.copy()的深浅拷贝陷阱 :df2 = df.copy()是浅拷贝,修改df2的列(如df2['A'] = ...)不影响df;但修改df2的子集(如df2_sub = df2.iloc[:10]; df2_sub['A'] = 999)可能影响df2,因为iloc返回视图。安全做法:df2_sub = df2.iloc[:10].copy()。 -
inplace=True的连锁反应 :df.dropna(inplace=True)后,df的index变成不连续的RangeIndex(0, 998, 1)。如果后续有df.iloc[999],会报错。建议:df = df.dropna().reset_index(drop=True),显式重置索引。 -
merge()的笛卡尔积灾难 :pd.merge(df1, df2, on='key')时,如果df1['key']有重复,df2['key']也有重复,结果行数 =df1.key.count() * df2.key.count()。务必先df1['key'].nunique()和df2['key'].nunique(),确保 key 是唯一的,或用validate='one_to_one'参数让 Pandas 帮你检查。
4.4 我的个人工作流模板
每次新项目,我都会新建一个
00_setup.py
,里面固化这些:
# 00_setup.py
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore') # 屏蔽无关警告
# 设置全局选项
pd.set_option('display.max_columns', None) # 显示所有列
pd.set_option('display.max_rows', 50) # 显示最多50行
pd.set_option('display.float_format', '{:.3f}'.format) # 浮点数显示3位小数
pd.set_option('mode.chained_assignment', None) # 关闭 SettingWithCopyWarning(仅用于确定安全的场景)
# 常用函数
def load_data(filepath, **kwargs):
"""封装 read_csv,内置常用参数"""
return pd.read_csv(filepath, encoding='utf-8', low_memory=False, **kwargs)
def quick_eda(df):
"""一键 EDA 报告"""
print("=== SHAPE ==="); print(df.shape)
print("\n=== INFO ==="); df.info()
print("\n=== DESCRIBE NUMERIC ==="); print(df.describe())
print("\n=== DESCRIBE OBJECT ==="); print(df.describe(include='object'))
print("\n=== MISSING VALUES ==="); print(df.isna().sum())
然后在主脚本中
from 00_setup import *
。这让我在任何新项目中,5分钟内就能进入状态,而不是反复查文档配环境。
5. 从 landslide 数据集看完整分析闭环:一个可运行的端到端示例
现在,我们把前面所有知识点串起来,用 landslide 数据集走一遍从加载到初步建模准备的完整链路。这段代码可以直接复制运行(需提前下载数据):
# 1. 加载与探测
import pandas as pd
import numpy as np
# 探测前5行
sample = pd.read_csv("landslide.csv", nrows=5)
print("Sample data:")
print(sample)
print("\nSample dtypes:")
print(sample.dtypes)
# 2. 精准加载
df = pd.read_csv(
"landslide.csv",
encoding='utf-8',
na_values=['NULL', 'N/A', '?', ''],
dtype={
'Lithology': 'category',
'Landuse': 'category',
'Earthquake': 'boolean',
'Landslide': 'boolean'
},
low_memory=False
)
# 3. 初步清洗
print(f"\nOriginal shape: {df.shape}")
print(f"Missing values per column:\n{df.isna().sum()}")
# 填充 Lithology(用众数)
litho_mode = df['Lithology'].mode()[0]
df['Lithology'].fillna(litho_mode, inplace=True)
# Earthquake 缺失,按领域知识填 False(无记录即无地震)
df['Earthquake'].fillna(False, inplace=True)
# 4. 探索性分析
print(f"\nAfter cleaning shape: {df.shape}")
print(f"\nLandslide distribution:\n{df['Landslide'].value_counts()}")
# 相关性热力图(用 seaborn,但数据来自 pandas)
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
# 只对数值列计算相关性
numeric_df = df.select_dtypes(include=[np.number])
sns.heatmap(numeric_df.corr(), annot=True, cmap='coolwarm', center=0)
plt.title("Feature Correlation Matrix")
plt.show()
# 5. 特征工程
# One-hot encode categorical
df = pd.get_dummies(df, columns=['Lithology', 'Landuse'], drop_first=True)
# 构造交互特征
df['Slope_X_Earthquake'] = df['Slope'] * df['Earthquake'].astype(int)
# 6. 准备模型输入
# 分离特征与目标
X = df.drop('Landslide', axis=1)
y = df['Landslide']
# 确保 X 中无 object 列(所有列都应是数值或 bool)
print(f"\nFinal feature matrix shape: {X.shape}")
print(f"Feature dtypes:\n{X.dtypes}")
# 输出到 csv,供后续模型使用
X.to_csv("landslide_features.csv", index=False)
y.to_csv("landslide_target.csv", index=False)
print("\n✅ Features and target saved for modeling!")
运行这段代码,你会得到:
-
一个清晰的
landslide_features.csv,所有特征已编码、已缩放、已构造; - 一个 `landslide

424

被折叠的 条评论
为什么被折叠?



