文章目录
——24年更新第3版
版本信息:
python 3.11
pandas 2.2.2
numpy 1.23.5
适合群体:
- 入门python的小白
- 纯小白但是想学习数据分析但没有合适的项目案例&覆盖最常见卡点
内容特点:
- 从excel使用习惯入手
- 最终结果是实现excel透视表功能 + 透视表不方便做的功能需求
- 以及为了实现透视表,所需要的过程。包括复杂的文件拼接合并、查找、替换、中间过程字段标签的生成。
整体介绍:
围绕数据的统计和透视的需求,包括数据准备处理阶段、数据透视阶段、和透视表复杂的计算和优化3阶。数据kaggle下载链接
- 要了解什么是Pandas.Datafame?以csv为例。(主要了解datafram的index和columns。建议通过excel透视表来理解)
- 数据准备阶段:如何快速合并N个文件 + 如何拆分成N个文件
- 如何进一步的视线excel的查找/筛选,替换,新建字段
- 如何实现exce透视表的统计分析 & 进阶
附注
现象:线下数据文档通常以csv格式存在,无论是kaggle竞赛 还是公司数据库返回的数据结果。
原因:csv读写速度比excel快,虽然相同文件excel的体积要小上不少。
文件 | 电脑配置 | Python | Pandas |
---|---|---|---|
320MB foo.csv,16MB foo.xlsx | i7 - 7700k, SSD | Anacond Python 3.5.3 | Pandas 0.19.2 |
用时 | |
---|---|
读取df = pd.read_csv('foo.csv') | 2 秒 |
读取df = pd.read_excel('foo.xlsx) | 15.3秒 |
写入 df.to_csv('hehe.csv', index=False) | 10.5 秒 |
写入 df.to_excel('hehe.xlsx', sheet_name='sheet1', index='False') | 34.5 秒 |
一、数据准备阶段:如何快速合并N个文件 + 如何拆分成N个文件
df_store =pd.read_csv(file_path+'stores.csv')
df_train =pd.read_csv(file_path+'train.csv')
# 文件store的前5行展示
print(df_stores.head())
Store Type Size
0 1 A 151315
1 2 A 202307
2 3 B 37392
3 4 A 205863
4 5 B 34875
# 文件train的前5行展示
print(df_train.head())
Store Dept Date Weekly_Sales IsHoliday
0 1 1 2010-02-05 24924.50 False
1 1 1 2010-02-12 46039.49 True
2 1 1 2010-02-19 41595.55 False
3 1 1 2010-02-26 19403.54 False
4 1 1 2010-03-05 21827.90 False
需求分类: 一般是2种:
1.1 excel多个文件合并1个。 e.g.分月的文件合并成一年的需求:
1.2 excel的vlook的功能。e.g. 按照store分别储存 & 给到对应的销售侧同事
1.1 N个文件合并
1.1.1 excel常见的有 e.g.分月的文件合并成一年的需求:
my_df_list = [df_train.head(), df_train.head()]
pd.concat(my_df_list)
# output
Store Dept Date Weekly_Sales IsHoliday
0 1 1 2010-02-05 24924.50 False
1 1 1 2010-02-12 46039.49 True
2 1 1 2010-02-19 41595.55 False
3 1 1 2010-02-26 19403.54 False
4 1 1 2010-03-05 21827.90 False
0 1 1 2010-02-05 24924.50 False
1 1 1 2010-02-12 46039.49 True
2 1 1 2010-02-19 41595.55 False
3 1 1 2010-02-26 19403.54 False
4 1 1 2010-03-05 21827.90 False
- concat函数介绍<函数官方文档链接>
pandas.concat(objs, # 通常为list对象
axis=0, # concat操作沿着哪个axis的方向来。参数0/行,1/列 任选其一;
jion='outer', # 参数'inner'和'outer'任选其一
**)
1.1.2 excel类似mult - vlook的拼接功能
train_merge_store = df_train.merge(df_store, on='Store', how='left')
train_merge_store.head()
# output
Store Dept Date Weekly_Sales IsHoliday Type Size
0 1 1 2010-02-05 24924.50 False A 151315
1 1 1 2010-02-12 46039.49 True A 151315
2 1 1 2010-02-19 41595.55 False A 151315
3 1 1 2010-02-26 19403.54 False A 151315
4 1 1 2010-03-05 21827.90 False A 151315
1.1.3 假如我有N个文件,堆在1个文件夹。我怎么快速地识别&合并呢?
# 显示某文件夹所有文件
file_path = 'your_path_name'
for file in os.listdir(file_path):
print(file)
# outpot
## 我的文件夹下所有的文件
python_统计分析应用和案例_xfhan2024M7.pdf
stores.csv
train - 副本.csv
train.csv
train_透视表展示.xlsx
其它.xlsx
file_path = 'your_path_name'
df_list = [] # 空list用于concat
for file in os.listdir(file_path): # 逐个读取文件
if file.endswith('.csv'): # 只选择csv格式文件
print(file)
df = pd.read_csv(file_path+f'/{file}', low_memory=False)
df_list.append(df.head(3)) # 为了测试的目的,只取每个df的前3行合并
print(file+'is done. \n')
print(file)
# output
stores.csv
stores.csv is done.
train - 副本.csv
train - 副本.csv is done.
train.csv
train.csv is done.
pd.concat(df_list)
# output
Store Type Size Dept Date Weekly_Sales IsHoliday
0 1 A 151315.0 NaN NaN NaN NaN
1 2 A 202307.0 NaN NaN NaN NaN
2 3 B 37392.0 NaN NaN NaN NaN
0 1 NaN NaN 1.0 2010-02-05 24924.50 False
1 1 NaN NaN 1.0 2010-02-12 46039.49 True
2 1 NaN NaN 1.0 2010-02-19 41595.55 False
0 1 NaN NaN 1.0 2010-02-05 24924.50 False
1 1 NaN NaN 1.0 2010-02-12 46039.49 True
2 1 NaN NaN 1.0 2010-02-19 41595.55 False
1.2 文件拆分N个
需求:按照sotre的编码拆分,然后各自保存
步骤1 查看Stroe有哪些值 & 类型
df_train.Store.unique() # unique查看唯一值
# output
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45])
步骤2 选用query函数或者mask方法 & 测试编写代码是否OK
store_num=1
mask = df_train['Store'] == store_num
df_train[mask].Store.unique() # 等价于df_train.query('Store==1')['Store'].unique()
# output
array([1]) # 可见底码OK
步骤3 终版代码编写来解决问题
store_num_list = df_train.Store.unique()
for store_num in store_num_list:
print(df_train[ df['Store']==store_num ].Store.unique() # 等价于df_train.query(f'Store=={store_num}').Store.unique()
# df_train[ df['Store']==store_num ].to_csv(your_file_path + f'{store_num}.csv', encoding='utf-8-sig', index=False)
# 注意: encoding='utf-8-sig'为中文储存编码
二、如何进一步的实现excel的查找/筛选,替换,新建字段
train_merge_store.head()
# output
Store Dept Date Weekly_Sales IsHoliday Type Size
0 1 1 2010-02-05 24924.50 False A 151315
1 1 1 2010-02-12 46039.49 True A 151315
2 1 1 2010-02-19 41595.55 False A 151315
3 1 1 2010-02-26 19403.54 False A 151315
4 1 1 2010-03-05 21827.90 False A 151315
2.1 按照列查找/筛选
train_merge_store.loc[:, ['Store', 'Weekly_Sales', 'Dept', 'Type']] # 等价于train_merge_store[['Store', 'Weekly_Sales', 'Dept', 'Type']]
2.2 按照行查找/筛选
train_merge_store.loc[train_merge_store['Store']==store_num] # 等价于train_merge_store.query(f'Store=={store_num}')
2.3 多条件查询/筛选
e.g.1 按行 & 列
train_merge_store.loc[train_merge_store['Store']==store_num, ['Store', 'Weekly_Sales', 'Dept', 'Type']]
e.g.2 下钻
mask_1 = train_merge_store['Store'] == 1
mask_2 = train_merge_store['IsHoliday'] == True
train_merge_store.loc[mask_1 & mask_2 ]
# output
Store Dept Date Weekly_Sales IsHoliday Type Size
1 1 1 2010-02-12 46039.49 True A 151315
31 1 1 2010-09-10 18194.74 True A 151315
42 1 1 2010-11-26 18820.29 True A 151315
47 1 1 2010-12-31 19124.58 True A 151315
53 1 1 2011-02-11 37887.17 True A 151315
... ... ... ... ... ... ... ...
10216 1 99 2011-09-09 40.00 True A 151315
10217 1 99 2011-11-25 2400.00 True A 151315
10221 1 99 2011-12-30 400.00 True A 151315
10223 1 99 2012-02-10 1070.00 True A 151315
10240 1 99 2012-09-07 0.05 True A 151315
723 rows × 7 columns
2.4 替换/更新目标条件下的值
e.g. Store为1 同时 IsHoliday为True部分,对应的Weekly_Sales替换为0
a = train_merge_store.copy() # 这样保持train_merge_store数据不变,便于后续使用
a.loc[mask_1 & mask_2, 'Weekly_Sales']=1
# output a.head()
Store Dept Date Weekly_Sales IsHoliday Type Size
0 1 1 2010-02-05 24924.50 False A 151315
1 1 1 2010-02-12 1.00 True A 151315
2 1 1 2010-02-19 41595.55 False A 151315
3 1 1 2010-02-26 19403.54 False A 151315
4 1 1 2010-03-05 21827.90 False A 151315
2.5 生成新的字段的标签呢?
e.g. 把store的Trype为A的打标为“大商超”,B的“超市”,C的“小便利店”
方法1。思路:先生成在修改。适合替换频次较少条件的情况
train_merge_store['Type中文名'] = train_merge_store.Type.copy()
train_merge_store.loc[train_merge_store['Type中文名']=='A', 'Type中文名']='大商超'
train_merge_store.loc[train_merge_store['Type中文名']=='B', 'Type中文名']='超市'
train_merge_store.loc[train_merge_store['Type中文名']=='C', 'Type中文名']='小便利店'
方法2。思路:用numpy.select。适合多条件&复杂条件,多种判定逻辑的情况。并且由于使用numpy处理速度回更快
mask_dsc = train_merge_store['Type']=='A'
mask_cs = train_merge_store['Type']=='B'
train_merge_store['Type中文名']=np.select(
[mask_dsc, mask_cs], # 条件列表
['大商超', '超市'], # 结果列表
default='小便利店' # 兜底逻辑。这是很有用的地方。
)
train_merge_store[['Type中文名', 'Type']].drop_duplicates() # 检查结果
# output
Type中文名 Type
0 大商超 A
20482 超市 B
286548 小便利店 C
三、如何实现excel透视表的统计分析
首先进一步的进行数据预处理
train_merge_store['Date'] = pd.to_datetime(train_merge_store['Date'], format='%Y-%m-%d')
train_merge_store['Year'] = train_merge_store['Date'].dt.year
train_merge_store['quarter'] = train_merge_store['Date'].dt.quarter
train_merge_store.head()
# output
Store Dept Date Weekly_Sales IsHoliday Type Size Type中文名 Year quarter
0 1 1 2010-02-05 24924.50 False A 151315 大商超 2010 1
1 1 1 2010-02-12 46039.49 True A 151315 大商超 2010 1
2 1 1 2010-02-19 41595.55 False A 151315 大商超 2010 1
3 1 1 2010-02-26 19403.54 False A 151315 大商超 2010 1
4 1 1 2010-03-05 21827.90 False A 151315 大商超 2010 1
3.1 基础透视表
pt = train_merge_store.pivot_table(index=['Type中文名', 'quarter'], columns='Year', values='Weekly_Sales', aggfunc='sum')
3.2 增加行总计
pt['All'] = pt.sum(axis=1)
3.3 增加行总计百分比
pt['%_total_quarter'] = pt['All']/ pt['All'].sum()
3.4 增加按照Type大小的分组 行总计百分比
pt['%_level_quarter'] = pt['All'] / pt.groupby(level=0)['All'].sum()
3.5 筛选出type里的销售额前2的季度
pt['All'].groupby(level=0, group_keys=False).nlargest(2)
# output
Type中文名 quarter
大商超 3 2.370451e+09
2 2.349429e+09
小便利店 3 2.248494e+08
2 2.224036e+08
超市 3 1.088404e+09
2 1.081397e+09
Name: All, dtype: float64
附注
该函数该如何理解?
首先 level=0什么意思?答:在grouby函数中,必须指定一个维度进行group,可以是按照index的直接的字段名,也可是按照index的level层级
其次 不设置groupby_keys=False会怎样?会出现2个’Type中文名’index。
还有另外一种方法,即通过sort_values和head函数实现,具体见<链接>
3.6 进行排名
pt['rank'] = pt['All'].groupby('Type中文名').rank(method='first', ascending=True)
附注 pandas.DataFrame.rank() method参数
first:类似SQL的 ROW_NUMBER(),即使有相同的数字也会一次升序/降序反馈排名
dense:类似SQL的 DENSE_RANK(),
min:类似SQL的 RANK(),
average:
df = pd.DataFrame(data={'Animal': ['cat', 'penguin','dog', 'spider', 'snake'],
'Number_legs': [4,2,4,8,np.nan]})
df['default_rank'] =df['Number_legs'].rank() # 'average'
df['max_rank'] =df['Number_legs'].rank(method='max')
df['min_rank'] =df['Number_legs'].rank(method='min')
df['dense_rank'] =df['Number_legs'].rank(method='dense')
df['first_rank'] =df['Number_legs'].rank(method='first')
3.7 获取最大值&新建最大值列
pt['max'] = pt.groupby('Type中文名')['All'].transform('max')
3.8 获取&新建sub level total
subtotal = pt.groupby('Type中文名').sum()
subtotal.index = pd.MultiIndex.from_tuples( (i, 'subtotal') for i in subtotal.index)
pd.concat([pt, subtotal])
3.9 如何优化排序?
pd.concat([pt, subtotal]).sort_index(ascending=True) # ascending=True即升序排序
# 除了按照index排序外,pandas还提供了sort_values,可以按照某行/某些行进行升序降序排序
四、以上即为python在替换excel统计统计分析上的常用逻辑和代码。此外还有一些常见的疑问解析
4.1 axis=1或者axis=0的易混淆点
test = pd.DataFrame({'col1': [42,13, np.nan, np.nan],
'col2': [4,12, np.nan, np.nan],
'col3': [25,61, np.nan, np.nan]})
print(test, '\n', '*'*10, '\n',
test.sum(axis=0), '\n', '*'*10, '\n',
test.dropna(axis=0, how='any'), '\n', '*'*10, '\n',
)
## 情况一 数字运算,axis=0意味着along the rows(column wise)
## 情况二 删减,axis=0意味着row wise
# output
col1 col2 col3
0 42.0 4.0 25.0
1 13.0 12.0 61.0
2 NaN NaN NaN
3 NaN NaN NaN
**********
col1 55.0
col2 16.0
col3 86.0
dtype: float64
**********
col1 col2 col3
0 42.0 4.0 25.0
1 13.0 12.0 61.0
**********
4.2 apply和transform的区别
4.2.1 transfrom无法进行聚合,apply不一定
pt.groupby('Type中文名')['All'].apply('max') # 作为对比pt.groupby('Type中文名')['All'].transform('max')返回的行数则和pt一样
## 所以transform可以这样使用,但apply不行。用于获取最大值
pt[ pt.groupby('Type中文名')['All'].transform('max') == pt['All'] ]
# output
Year 2010 2011 2012 All %_total_quarter %_level_quarter rank max
Type中文名 quarter
大商超 3 3.826937e+08 4.133643e+08 3.891675e+08 2.370451e+09 0.175922 0.273660 4.0 2.370451e+09
小便利店 3 3.629038e+07 3.849747e+07 3.763684e+07 2.248494e+08 0.016687 0.277247 4.0 2.248494e+08
超市 3 1.785041e+08 1.874967e+08 1.782014e+08 1.088404e+09 0.080775 0.272006 4.0 1.088404e+09
4.2.2 apply允许对dataframe的多个列进行操作,而transform不行
def mu_func(df):
return df['All'] - df['max']
pt.groupby(level=0).apply(mu_func)
4.3 pivot_table透视表还可以方面的同时进行包括但不限于 非重复计数、汇总等操作
train_merge_store.pivot_table(index=['Type中文名', 'quarter'],
columns='Year',
values=['Weekly_Sales', 'IsHoliday', 'Date', 'Type'],
aggfunc={'Weekly_Sales': 'sum', # 求和
'IsHoliday': set, # 求不重复值
'Date': 'count', # 求频次/计数
'Type': 'nunique'}) # 求非重复统计值
五、 后续思考
1)在透视表中,是否可以把字段’IsHoliday’由现在单行单个cell的N个值,展开成N行的1个cell1个值? 参考答案<链接>
2)在文本datafram中,是否可以标注出是否包含某些关键词?例如某列的值’ABCD’是否包含关键词’AB’和’CD’?
总结:快速准确的做到统计分析,是更集中地思考数据分析应用和可视化呈现的基础。也是跳出工具人的必要前提。