Pandas核心能力地图:Data Manipulation与自动对齐原理

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值