数据分析师的工作中,80%的时间都在清洗和整理数据,只有20%的时间用于真正的分析。而pandas,这个诞生于2008年的Python数据分析库,正是为了解决这个痛点而生。本文将带你系统掌握pandas的核心操作,让数据处理效率提升10倍。
一、pandas核心数据结构深度解析
Series:一维数据的处理
Series是pandas中最基础的数据结构,可以理解为带标签的一维数组。与NumPy数组不同,Series的每个元素都有对应的索引标签。
import pandas as pd
import numpy as np
# 创建Series的多种方式
s1 = pd.Series([1, 3, 5, 7, 9])
s2 = pd.Series([1, 3, 5, 7, 9], index=['a', 'b', 'c', 'd', 'e'])
s3 = pd.Series({'北京': 2170, '上海': 2487, '深圳': 1756})
s4 = pd.Series(100, index=['a', 'b', 'c'])
# Series的核心属性
print(s2.values) # 获取值数组
print(s2.index) # 获取索引
print(s2.dtype) # 获取数据类型
print(s2.shape) # 获取形状
Series支持向量化运算和标签索引,这使得它在处理时间序列数据时特别强大:
# 向量化运算
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
result = s * 2 + 10# 所有元素参与运算
print(result)
# 布尔索引
print(s[s > 20]) # 筛选大于20的元素
# 标签索引
print(s['b']) # 通过标签访问
print(s.loc['b':'d']) # 标签切片(包含结尾)
print(s.iloc[1:3]) # 位置切片(不包含结尾)
DataFrame:二维数据的处理
DataFrame是pandas的核心,可以看作是由多个Series组成的字典,或者是带标签的二维数组。
# 创建DataFrame的多种方式
# 方式1:从字典创建
data = {
'name': ['张三', '李四', '王五', '赵六'],
'age': [25, 30, 35, 28],
'city': ['北京', '上海', '广州', '深圳'],
'salary': [8000, 12000, 15000, 10000]
}
df = pd.DataFrame(data)
# 方式2:从嵌套列表创建
df2 = pd.DataFrame(
[['张三', 25, '北京'], ['李四', 30, '上海']],
columns=['name', 'age', 'city']
)
# 方式3:从字典的列表创建
df3 = pd.DataFrame([
{'name': '张三', 'age': 25},
{'name': '李四', 'age': 30}
])
# DataFrame的核心属性
print(df.shape) # (行数, 列数)
print(df.columns) # 列名
print(df.index) # 行索引
print(df.dtypes) # 各列数据类型
print(df.info()) # 数据概览
print(df.describe()) # 统计描述
二、数据读写
读取各类数据源
pandas支持几乎所有主流的数据格式,这是它成为数据分析首选工具的重要原因。
# CSV文件读取
df = pd.read_csv('data.csv', encoding='utf-8')
df = pd.read_csv('data.csv', sep='\t') # 指定分隔符
df = pd.read_csv('data.csv', header=None, names=['col1', 'col2']) # 自定义列名
df = pd.read_csv('data.csv', index_col=0) # 指定索引列
df = pd.read_csv('data.csv', usecols=['col1', 'col2']) # 只读取特定列
df = pd.read_csv('data.csv', nrows=1000) # 只读取前1000行
df = pd.read_csv('data.csv', dtype={'col1': str}) # 指定数据类型
df = pd.read_csv('data.csv', parse_dates=['date_col']) # 解析日期
# Excel文件读取
df = pd.read_excel('data.xlsx', sheet_name='Sheet1')
df = pd.read_excel('data.xlsx', sheet_name=0) # 通过索引指定sheet
dfs = pd.read_excel('data.xlsx', sheet_name=None) # 读取所有sheet
# JSON文件读取
df = pd.read_json('data.json')
df = pd.read_json('data.json', orient='records')
# SQL数据库读取
import sqlite3
conn = sqlite3.connect('database.db')
df = pd.read_sql('SELECT * FROM table_name', conn)
df = pd.read_sql_query('SELECT * FROM table WHERE id > 100', conn)
df = pd.read_sql_table('table_name', conn)
# HTML表格读取
dfs = pd.read_html('https://example.com/table.html')
# 剪贴板读取
df = pd.read_clipboard()
数据写入与导出
# CSV写入
df.to_csv('output.csv', index=False) # 不保存索引
df.to_csv('output.csv', encoding='utf-8-sig') # 解决中文乱码
df.to_csv('output.csv', columns=['col1', 'col2']) # 只导出特定列
# Excel写入
df.to_excel('output.xlsx', sheet_name='Sheet1', index=False)
# 多个DataFrame写入同一个Excel
with pd.ExcelWriter('output.xlsx') as writer:
df1.to_excel(writer, sheet_name='Sheet1')
df2.to_excel(writer, sheet_name='Sheet2')
# JSON写入
df.to_json('output.json', orient='records', force_ascii=False)
# SQL写入
df.to_sql('table_name', conn, if_exists='replace', index=False)
# if_exists参数: 'fail', 'replace', 'append'
# HTML写入
df.to_html('output.html')
# 剪贴板写入
df.to_clipboard(index=False)
三、数据选择与索引:精准定位每一个数据
列选择的多种方式
# 单列选择
df['name'] # 返回Series
df.name # 属性方式访问
# 多列选择
df[['name', 'age']] # 返回DataFrame
# 条件选择
df[df['age'] > 30] # 布尔索引
df[(df['age'] > 25) & (df['salary'] > 10000)] # 多条件
df[df['city'].isin(['北京', '上海'])] # 包含于列表
# 字符串条件
df[df['name'].str.contains('张')]
df[df['name'].str.startswith('李')]
df[df['name'].str.endswith('三')]
loc与iloc:标签定位vs位置定位
# loc: 基于标签的索引
df.loc[0] # 选择第0行
df.loc[0:2] # 行切片(包含结尾)
df.loc[0, 'name'] # 选择特定单元格
df.loc[0:2, ['name', 'age']] # 行列同时选择
df.loc[df['age'] > 30, 'name'] # 条件选择特定列
# iloc: 基于位置的索引
df.iloc[0] # 第0行
df.iloc[0:2] # 行切片(不包含结尾)
df.iloc[0, 1] # 第0行第1列
df.iloc[0:2, 0:2] # 行列位置选择
df.iloc[:, -1] # 最后一列
# at与iat: 快速访问单个值
df.at[0, 'name'] # 快速标签访问
df.iat[0, 1] # 快速位置访问
高级索引技巧
# 设置索引
df_indexed = df.set_index('name')
df_multi = df.set_index(['city', 'name']) # 多级索引
# 重置索引
df_reset = df_indexed.reset_index()
df_reset = df_indexed.reset_index(drop=True) # 删除原索引
# 多级索引访问
df_multi.loc[('北京', '张三')]
df_multi.loc['北京'] # 访问外层索引
# 交叉选择
df.loc[df['age'].between(25, 35), :]
df.loc[df['salary'].isin([8000, 10000]), :]
# query方法:更优雅的查询
df.query('age > 30 and salary > 10000')
df.query('city in ["北京", "上海"]')
四、数据清洗
缺失值处理
# 检测缺失值
df.isnull() # 返回布尔DataFrame
df.isnull().sum() # 统计每列缺失值数量
df.isnull().sum().sum() # 总缺失值数量
# 删除缺失值
df.dropna() # 删除包含缺失值的行
df.dropna(axis=1) # 删除包含缺失值的列
df.dropna(thresh=2) # 保留至少有2个非缺失值的行
df.dropna(subset=['age', 'salary']) # 只考虑特定列
# 填充缺失值
df.fillna(0) # 用0填充
df.fillna(method='ffill') # 前向填充
df.fillna(method='bfill') # 后向填充
df.fillna(df.mean()) # 用均值填充
df.fillna({'age': df['age'].mean(), 'salary': 0}) # 不同列不同填充
# 高级插值
df.interpolate() # 线性插值
df.interpolate(method='polynomial', order=2) # 多项式插值
df.interpolate(method='time') # 时间序列插值
重复值处理
# 检测重复值
df.duplicated() # 标记重复行
df.duplicated(subset=['name']) # 基于特定列检测
df.duplicated(keep='last') # 保留最后一次出现
# 删除重复值
df.drop_duplicates()
df.drop_duplicates(subset=['name'], keep='first')
df.drop_duplicates(subset=['name', 'age'])
异常值处理
# 基于统计方法检测异常值
defdetect_outliers_iqr(df, column):
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
return df[(df[column] < lower_bound) | (df[column] > upper_bound)]
# 基于Z-score检测异常值
from scipy import stats
df['z_score'] = np.abs(stats.zscore(df['salary']))
outliers = df[df['z_score'] > 3]
# 替换异常值
df.loc[df['salary'] > 100000, 'salary'] = df['salary'].median()
# 截断异常值
df['salary'] = df['salary'].clip(lower=5000, upper=50000)
数据类型转换
# 基本类型转换
df['age'] = df['age'].astype(int)
df['salary'] = df['salary'].astype(float)
df['name'] = df['name'].astype(str)
# 转换为分类类型(节省内存)
df['city'] = df['city'].astype('category')
# 转换为日期类型
df['date'] = pd.to_datetime(df['date'])
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
df['date'] = pd.to_datetime(df['date'], errors='coerce') # 无效值转为NaT
# 时间戳转换
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s')
# 数值转换
df['salary'] = pd.to_numeric(df['salary'], errors='coerce')
五、数据转换
列操作
# 新增列
df['bonus'] = df['salary'] * 0.1
df['total'] = df['salary'] + df['bonus']
# 条件赋值
df['level'] = np.where(df['age'] > 30, 'senior', 'junior')
df['grade'] = pd.cut(df['salary'], bins=[0, 8000, 12000, 20000],
labels=['C', 'B', 'A'])
# 多条件赋值
conditions = [
df['salary'] < 8000,
(df['salary'] >= 8000) & (df['salary'] < 12000),
df['salary'] >= 12000
]
choices = ['初级', '中级', '高级']
df['position'] = np.select(conditions, choices, default='未知')
# apply应用函数
df['tax'] = df['salary'].apply(lambda x: x * 0.1if x > 10000else0)
df['name_upper'] = df['name'].apply(str.upper)
# map映射
city_map = {'北京': 'BJ', '上海': 'SH', '广州': 'GZ', '深圳': 'SZ'}
df['city_code'] = df['city'].map(city_map)
# 删除列
df.drop('column_name', axis=1, inplace=True)
df.drop(['col1', 'col2'], axis=1, inplace=True)
# 重命名列
df.rename(columns={'old_name': 'new_name'}, inplace=True)
df.columns = ['新列名1', '新列名2', '新列名3']
行操作
# 新增行
new_row = pd.DataFrame([['钱七', 32, '杭州', 11000]],
columns=df.columns)
df = pd.concat([df, new_row], ignore_index=True)
# 删除行
df.drop(0, inplace=True) # 删除索引为0的行
df.drop([0, 1, 2], inplace=True) # 删除多行
df = df[df['age'] > 25] # 条件删除
# 排序
df.sort_values('age') # 升序
df.sort_values('age', ascending=False) # 降序
df.sort_values(['city', 'age']) # 多列排序
df.sort_values(['city', 'age'], ascending=[True, False])
df.sort_index() # 按索引排序
数据透视与重塑
# 透视表
pivot = df.pivot_table(
values='salary',
index='city',
columns='age',
aggfunc='mean'
)
# 多值透视
pivot = df.pivot_table(
values=['salary', 'bonus'],
index='city',
columns='position',
aggfunc={'salary': 'mean', 'bonus': 'sum'}
)
# 长格式转宽格式
df_wide = df.pivot(index='name', columns='year', values='salary')
# 宽格式转长格式
df_long = df_wide.melt(
id_vars='name',
var_name='year',
value_name='salary'
)
# 堆叠与反堆叠
stacked = df.stack() # 列索引转为行索引
unstacked = df.unstack() # 行索引转为列索引
字符串操作
# 基本字符串方法
df['name'].str.lower() # 转小写
df['name'].str.upper() # 转大写
df['name'].str.len() # 字符串长度
df['name'].str.strip() # 去除首尾空格
df['name'].str.replace('张', '李') # 替换
# 分割与合并
df['name'].str.split(' ') # 分割
df['first_name'] = df['name'].str.split(' ').str[0] # 取分割后的第一个
df['full_name'] = df['first_name'] + ' ' + df['last_name'] # 合并
# 包含与匹配
df[df['name'].str.contains('张')]
df[df['name'].str.startswith('李')]
df[df['email'].str.match(r'^\w+@\w+\.\w+$')] # 正则匹配
# 提取
df['area_code'] = df['phone'].str.extract(r'(\d{3})-\d{8}')
df[['year', 'month', 'day']] = df['date'].str.extract(r'(\d{4})-(\d{2})-(\d{2})')
六、数据分组与聚合
groupby分组操作
# 单列分组
grouped = df.groupby('city')
grouped.mean() # 各城市的平均值
grouped.sum() # 各城市的总和
grouped.count() # 各城市的计数
grouped.size() # 各组的大小
# 多列分组
df.groupby(['city', 'position']).mean()
# 指定聚合列
df.groupby('city')['salary'].mean()
df.groupby('city')[['salary', 'bonus']].sum()
# 多种聚合函数
df.groupby('city').agg({
'salary': ['mean', 'min', 'max'],
'age': ['mean', 'std'],
'name': 'count'
})
# 自定义聚合函数
defcustom_range(x):
return x.max() - x.min()
df.groupby('city')['salary'].agg(custom_range)
# 命名聚合
df.groupby('city').agg(
avg_salary=('salary', 'mean'),
max_salary=('salary', 'max'),
total_employees=('name', 'count')
)
高级分组技巧
# 分组后筛选
df.groupby('city').filter(lambda x: x['salary'].mean() > 10000)
# 分组后转换
df['salary_pct'] = df.groupby('city')['salary'].transform(
lambda x: (x / x.sum()) * 100
)
# 分组排名
df['rank'] = df.groupby('city')['salary'].rank(ascending=False)
# 累计计算
df['cumsum'] = df.groupby('city')['salary'].cumsum()
df['cummax'] = df.groupby('city')['salary'].cummax()
# 移动窗口
df['rolling_mean'] = df.groupby('city')['salary'].rolling(window=3).mean()
# 分组应用自定义函数
deftop_n(group, n=2):
return group.nlargest(n, 'salary')
df.groupby('city').apply(top_n, n=3)
交叉表分析
# 基本交叉表
pd.crosstab(df['city'], df['position'])
# 带值的交叉表
pd.crosstab(df['city'], df['position'], values=df['salary'], aggfunc='mean')
# 带边际和的交叉表
pd.crosstab(df['city'], df['position'], margins=True)
# 归一化
pd.crosstab(df['city'], df['position'], normalize='index') # 行归一化
pd.crosstab(df['city'], df['position'], normalize='columns') # 列归一化
pd.crosstab(df['city'], df['position'], normalize='all') # 全局归一化
七、数据合并与连接
concat:简单拼接
# 纵向拼接
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})
result = pd.concat([df1, df2], ignore_index=True)
# 横向拼接
result = pd.concat([df1, df2], axis=1)
# 带键拼接
result = pd.concat([df1, df2], keys=['first', 'second'])
# 内连接方式拼接
result = pd.concat([df1, df2], join='inner')
merge:SQL式连接
# 内连接
pd.merge(df1, df2, on='key')
pd.merge(df1, df2, how='inner')
# 左连接
pd.merge(df1, df2, on='key', how='left')
# 右连接
pd.merge(df1, df2, on='key', how='right')
# 外连接
pd.merge(df1, df2, on='key', how='outer')
# 多键连接
pd.merge(df1, df2, on=['key1', 'key2'])
# 不同列名连接
pd.merge(df1, df2, left_on='lkey', right_on='rkey')
# 索引连接
pd.merge(df1, df2, left_index=True, right_index=True)
# 指示器
pd.merge(df1, df2, on='key', how='outer', indicator=True)
# 后缀处理
pd.merge(df1, df2, on='key', suffixes=('_left', '_right'))
join:基于索引连接
# 默认左连接
df1.join(df2)
# 指定连接方式
df1.join(df2, how='inner')
df1.join(df2, how='outer')
# 多DataFrame连接
df1.join([df2, df3, df4])
# 指定连接键
df1.join(df2, on='key')
八、时间序列的数据处理
时间序列创建
# 日期范围生成
dates = pd.date_range('2024-01-01', periods=100, freq='D')
dates = pd.date_range('2024-01-01', '2024-12-31', freq='W')
# 频率代码
# D: 日, W: 周, M: 月末, MS: 月初, Q: 季度末, QS: 季度初
# Y: 年末, YS: 年初, H: 小时, T/min: 分钟, S: 秒
# 营业日日期
bdate = pd.bdate_range('2024-01-01', periods=100)
# 时间戳
ts = pd.Timestamp('2024-01-01 12:00:00')
ts = pd.Timestamp.now()
# Period对象
p = pd.Period('2024-01', freq='M')
时间序列索引
# 设置时间索引
df = df.set_index(pd.to_datetime(df['date']))
# 时间选择
df['2024'] # 选择2024年
df['2024-01'] # 选择2024年1月
df['2024-01-01':'2024-01-31'] # 时间范围
# 时间切片
df.loc['2024-01-01']
df.loc['2024-01-01':'2024-06-30']
# 最近的值
df.asof('2024-01-15')
时间序列运算
# 时间差
df['days_diff'] = (df['end_date'] - df['start_date']).dt.days
# 时间提取
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['weekday'] = df['date'].dt.dayofweek
df['quarter'] = df['date'].dt.quarter
df['is_month_end'] = df['date'].dt.is_month_end
# 时间偏移
df['next_day'] = df['date'] + pd.Timedelta(days=1)
df['next_month'] = df['date'] + pd.DateOffset(months=1)
# 重采样
df.resample('M').mean() # 按月聚合
df.resample('W').sum() # 按周求和
df.resample('Q').last() # 按季度取最后值
# 滚动窗口
df['rolling_mean'] = df['value'].rolling(window=7).mean()
df['rolling_std'] = df['value'].rolling(window=7).std()
# 指数加权移动平均
df['ewm'] = df['value'].ewm(span=7).mean()
# 移位操作
df['prev_value'] = df['value'].shift(1) # 向下移1位
df['next_value'] = df['value'].shift(-1) # 向上移1位
df['pct_change'] = df['value'].pct_change() # 百分比变化
时区处理
# 本地化时区
df_tz = df.tz_localize('Asia/Shanghai')
# 转换时区
df_utc = df_tz.tz_convert('UTC')
# 去除时区
df_naive = df_tz.tz_localize(None)
九、数据可视化
基础图表
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文显示
plt.rcParams['axes.unicode_minus'] = False
# 线图
df.plot(x='date', y='value')
df.plot(x='date', y=['value1', 'value2'])
# 柱状图
df.plot(x='name', y='salary', kind='bar')
df.plot(x='name', y='salary', kind='barh') # 横向柱状图
# 散点图
df.plot(x='age', y='salary', kind='scatter')
# 饼图
df['city'].value_counts().plot(kind='pie', autopct='%1.1f%%')
# 箱线图
df.boxplot(column='salary', by='city')
# 直方图
df['salary'].plot(kind='hist', bins=20)
# 面积图
df.plot(x='date', y='value', kind='area')
# 多子图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
df.plot(ax=axes[0, 0], y='value1')
df.plot(ax=axes[0, 1], y='value2', kind='bar')
df.plot(ax=axes[1, 0], y='value3', kind='scatter', x='value1')
df['value4'].plot(ax=axes[1, 1], kind='hist')
plt.tight_layout()
高级可视化技巧
# 样式设置
df.plot(
figsize=(12, 6),
color='red',
linestyle='--',
linewidth=2,
marker='o',
markersize=5,
alpha=0.7,
grid=True,
legend=True,
title='销售趋势图'
)
# 双轴图
ax1 = df.plot(x='date', y='sales', color='blue')
ax2 = ax1.twinx()
df.plot(x='date', y='profit', color='red', ax=ax2)
# 堆叠图
df.plot(kind='bar', stacked=True)
df.plot(kind='area', stacked=True)
# 自定义图例
df.plot(legend={'loc': 'upper left', 'fontsize': 12})
十、高级技巧与性能优化
链式操作
# 方法链
result = (df
.query('age > 25')
.groupby('city')
.agg({'salary': 'mean'})
.sort_values('salary', ascending=False)
.head(10)
)
# pipe方法
defremove_outliers(df, column):
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
return df[(df[column] >= Q1 - 1.5*IQR) & (df[column] <= Q3 + 1.5*IQR)]
result = (df
.pipe(remove_outliers, 'salary')
.groupby('city')['salary']
.mean()
)
向量化操作优化
# 避免循环
# 慢
for i in range(len(df)):
df.loc[i, 'new_col'] = df.loc[i, 'col1'] * df.loc[i, 'col2']
# 快
df['new_col'] = df['col1'] * df['col2']
# 使用numpy
df['new_col'] = np.where(df['col1'] > 0, df['col2'], 0)
# 使用内置函数
df['col1'].sum() # 快于 sum(df['col1'])
内存优化
# 查看内存使用
df.info(memory_usage='deep')
df.memory_usage(deep=True)
# 优化数据类型
# 数值型优化
df['int_col'] = df['int_col'].astype('int32') # 从int64降低到int32
df['float_col'] = df['float_col'].astype('float32')
# 类别型优化
df['category_col'] = df['category_col'].astype('category')
# 批量优化
defreduce_mem_usage(df):
for col in df.columns:
col_type = df[col].dtype
if col_type != object:
c_min = df[col].min()
c_max = df[col].max()
if str(col_type)[:3] == 'int':
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
else:
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
df[col] = df[col].astype(np.float16)
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
return df
大数据处理
# 分块读取
chunk_size = 10000
chunks = []
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
# 处理每个chunk
processed = chunk[chunk['value'] > 0]
chunks.append(processed)
result = pd.concat(chunks, ignore_index=True)
# 只读取需要的列
df = pd.read_csv('file.csv', usecols=['col1', 'col2'])
# 指定数据类型
dtypes = {'col1': 'int32', 'col2': 'float32', 'col3': 'category'}
df = pd.read_csv('file.csv', dtype=dtypes)
# 使用Dask处理超大数据
import dask.dataframe as dd
ddf = dd.read_csv('large_file.csv')
result = ddf.groupby('column').mean().compute()
实用技巧
# 查看唯一值
df['column'].unique()
df['column'].nunique()
df['column'].value_counts()
# 条件计数
(df['salary'] > 10000).sum()
df.groupby('city')['salary'].apply(lambda x: (x > 10000).sum())
# 累计统计
df['cumsum'] = df['value'].cumsum()
df['cummax'] = df['value'].cummax()
df['cummin'] = df['value'].cummin()
# 百分位数
df['salary'].quantile([0.25, 0.5, 0.75])
# 相关性分析
df.corr() # 相关系数矩阵
df['col1'].corr(df['col2']) # 两列相关性
# 数据采样
df.sample(n=100) # 随机采样100行
df.sample(frac=0.1) # 采样10%的数据
df.sample(n=100, replace=True) # 有放回采样
# 数据验证
assert df['age'].min() >= 0, "年龄不能为负"
assert df['salary'].isnull().sum() == 0, "薪资列存在缺失值"
# 管道处理
defprocess_data(df):
return (df
.assign(bonus=lambda x: x['salary'] * 0.1)
.query('age > 25')
.sort_values('salary', ascending=False)
)
# 窗口函数
df['rank'] = df['salary'].rank(method='dense')
df['dense_rank'] = df.groupby('city')['salary'].rank(method='dense')
# 多列同时操作
df[['col1', 'col2', 'col3']] = df[['col1', 'col2', 'col3']].apply(lambda x: x * 2)
结语
pandas作为Python数据分析的核心工具,其强大之处在于它将复杂的数据操作转化为简洁的代码表达。从数据读取、清洗、转换,到分析、可视化,pandas提供了一整套完整的解决方案。
掌握pandas不是一蹴而就的过程,需要在实际项目中不断实践和积累。建议你按照本文的知识体系,先理解核心概念,再通过真实数据集反复练习。记住,最好的学习方式就是动手实践。