Pandas补充

为什么要补充?因为这篇文章总结的是23年左右的知识点,随着Pandas的不断更新以及更多业务场景的不断涌现,Pandas知识点就需要扩充了,而且重要的都标了⭐️,Respect!!

⭐️⭐️Pandas案例

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
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
# 如果是Mac本, 不支持SimHei的时候, 可以修改为 'Microsoft YaHei' 或者 'Arial Unicode MS'\n
plt.rcParams['axes.unicode_minus'] = False
# 设置显示风格
plt.style.use('fivethirtyeight')
plt.figure(figsize=(10, 8))

# 读取整个csv文件封装成DataFrame
df = pd.read_csv("/Users/lifuyao/PycharmProjects/pythonProject/Files/1960-2019全球GDP数据.csv", encoding='gbk')
# 只筛选中国的数据
china_df = df[df.country=="中国"]

# 设置年份为index(因为之前的是按照序号作为索引 inplace=True:直接修改原 DataFrame,而不是返回一个新的 DataFrame。
china_df.set_index('year', inplace=True)
china_df.rename(columns={'GDP':'中国'}, inplace=True)

china_df.中国.plot(legend=True)

usa_df = df[df.country=="美国"]
usa_df.set_index('year', inplace=True)
usa_df.rename(columns={'GDP':'美国'}, inplace=True)
usa_df.美国.plot(legend=True)


jp_df = df[df.country=="日本"]
jp_df.set_index('year', inplace=True)
jp_df.rename(columns={'GDP':'日本'}, inplace=True)
jp_df.日本.plot(legend=True)

绘图展示 :

image-20250629211403477

⭐️⭐️⭐️Series

创建

Series是什么?可以理解为向量

创建Series的方式呢?

  • 通过列表、字典、元组都可以作为pd.Series()的数据输入
  • 也可以使用numpy的arange函数也可以作为pd.Series()的数据输入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
import numpy as np

# Series能够传入Python中的列表,元组和字典, 而且可以自定义index,index指定必须为列表
s1 = pd.Series([1, 2, 3, 4, 5])

s2 = pd.Series([1, 2, 3, 4, 5], index=['a','b', 'c', 'd', 'e'])

s3 = pd.Series((11, 22, 33, 44, 55), index=['a','b', 'c', 'd', 'e'])

s4 = pd.Series({'a': 1, 'b': 2, 'c': 3})
s5 = pd.Series(np.arange(5))

s6 = pd.Series(data=[i for i in range(5)], index=[i for i in 'ABCDE'])
s6

Series属性和方法

1
2
3
4
5
6
# 练习Series的属性和方法(index, values, 根据索引获取value)
print(s6.index)
print(s6.values)
s6['E']
s6['E'] = 99
s6['E']

⭐️⭐️⭐️DataFrame

创建DataFrame

DataFrame是一个类似于二维数组或表格(如excel)的对象,既有行索引,又有列索引

  • 行索引,表明不同行,横向索引,叫index,0轴,axis=0
  • 列索引,表名不同列,纵向索引,叫columns,1轴,axis=1

image-20250630102430897

同Series一样,DataFrame也可以接收多种类型的数据并封装成DataFrame。

  • 从文件中读取:pd.read_csv(‘csv格式数据文件路径’)
  • 列表嵌套字典
  • 列表嵌套元组
  • 通过ndarray创建DataFrame
    • 想想numpy的创建Ndarray方法?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建DataFrame的三种方式,
# 列表嵌套字典(每个键值对是一列数据),列表嵌套元组(每个元组是一行数据),通过Numpy的ndarray创建
infoData={'name':['张三','李四','王五','赵六'],
'gender':['女','男','男','女'],
'age':[54,45,67,30]}
print(pd.DataFrame(data=infoData))

# 列表嵌套元组
info = [
('张三','女',39),
('李四','男',40),
('王五','女',20)]
df2 = pd.DataFrame(data=info, columns=['姓名', '性别', '年龄'])
df2

# 通过ndarray创建DataFrame
arr1 = np.arange(12).reshape(3, 4)
df3 = pd.DataFrame(data=arr1, columns=['a', 'b', 'c', 'd'])
df3

DataFrame属性

  • 查看shape属性

    • data.shape
  • 查看index属性

    • data.index
  • 查看 columns

    • data.columns
  • 查看values

    • data.values
  • 转置

    • data.T
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
import pandas as pd
import numpy as np

# 创建一个示例DataFrame
data = pd.DataFrame({
'姓名': ['张三', '李四', '王五'],
'年龄': [25, 30, 28],
'城市': ['北京', '上海', '广州']
})

print("原始DataFrame:")
print(data)

print("\nshape属性(行数,列数):")
print(data.shape) # 输出: (3, 3)

print("\nindex属性(行索引):")
print(data.index) # 输出: RangeIndex(start=0, stop=3, step=1)

print("\ncolumns属性(列名):")
print(data.columns) # 输出: Index(['姓名', '年龄', '城市'], dtype='object')

print("\nvalues属性(底层numpy数组):")
print(data.values)
"""
输出:
[['张三' 25 '北京']
['李四' 30 '上海']
['王五' 28 '广州']]
"""

print("\n转置后的DataFrame:")
print(data.T)
"""
输出:
0 1 2
姓名 张三 李四 王五
年龄 25 30 28
城市 北京 上海 广州
"""

DataFrame对象方法

对于大型数据集,可以先查看部分数据

  • large_data = pd.read_csv(‘big_dataset.csv’)
  • print(large_data.head(10)) # 查看前10行了解数据结构
  • print(large_data.tail(8)) # 查看后8行检查数据完整性
  • 显示前n行内容
    • head(n)
  • 显示后n行内容(如果不补充参数,默认5行。填入参数n则显示后n行)
    • data.tail(5)
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
46
47
48
49
50
51
52
53
54
55
56
import pandas as pd

# 创建一个包含20行数据的DataFrame
data = pd.DataFrame({
'ID': range(1, 21),
'Value': [i*10 for i in range(1, 21)],
'Category': ['A', 'B', 'C']*6 + ['A', 'B']
})

print("完整DataFrame (20行):")
print(data)

# 查看前5行(默认)
print("\n默认head() - 前5行:")
print(data.head())
"""
ID Value Category
0 1 10 A
1 2 20 B
2 3 30 C
3 4 40 A
4 5 50 B
"""

# 查看前3行
print("\nhead(3) - 前3行:")
print(data.head(3))
"""
ID Value Category
0 1 10 A
1 2 20 B
2 3 30 C
"""


# 查看后5行(默认)
print("\n默认tail() - 后5行:")
print(data.tail())
"""
ID Value Category
15 16 160 A
16 17 170 B
17 18 180 C
18 19 190 A
19 20 200 B
"""

# 查看后3行
print("\ntail(3) - 后3行:")
print(data.tail(3))
"""
ID Value Category
17 18 180 C
18 19 190 A
19 20 200 B
"""

DatatFrame索引的设置

需求:

image-20250630104653161

  • 1- 修改行列索引值
1
2
3
4
stu = ["学生_" + str(i) for i in range(score_df.shape[0])]

# 必须整体全部修改
data.index = stu

注意:以下修改方式是错误的

1
2
# 错误修改方式
data.index[3] = '学生_3'
  • 2- 重设索引
    • reset_index(drop=False)
      • 设置新的下标索引
      • drop:默认为False,不删除原来索引,如果为True,删除原来的索引值
1
2
# 重置索引,drop=False
data.reset_index()
  • 以某列值设置为新的索引
    • set_index(keys, drop=True)
      • keys : 列索引名成或者列索引名称的列表
      • drop : boolean, default True.当做新的索引,删除原来的列
1
2
# 重置索引,drop=True
data.reset_index(drop=True)

代码

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
df = pd.DataFrame({'month': [1, 4, 7, 10],
'year': [2012, 2014, 2013, 2014],
'sale':[55, 40, 84, 31]})

# month sale year
# 0 1 55 2012
# 1 4 40 2014
# 2 7 84 2013
# 3 10 31 2014

df.set_index('month')
# sale year
# month
# 1 55 2012
# 4 40 2014
# 7 84 2013
# 10 31 2014

df = df.set_index(['year', 'month'])
df
# sale
# year month
# 2012 1 55
# 2014 4 40
# 2013 7 84
# 2014 10 31

Pandas数据类型

Pandas数据类型 说明 对应的Python类型
Object 字符串类型 string
int 整数类型 int
float 浮点数类型 float
datetime 日期时间类型 datetime包中的datetime类型
timedelta 时间差类型 datetime包中的timedelta类型
category 分类类型 无原生类型,可以自定义
bool 布尔类型 bool(True,False)
nan 空值类型 None
  • 可以通过下列API查看s对象或df对象中数据的类型
1
2
3
s1.dtypes
df1.dtypes
df1.info() # s对象没有info()方法
  • 几个特殊类型演示

    • datetime类型
    1
    2
    3
    4
    5
    import pandas as pd

    # 创建一个datetime类型的Series
    dates = pd.to_datetime(['2024-09-01', '2024-09-02', '2024-09-03'])
    print(dates)
    • timedelta类型
    1
    2
    3
    4
    5
    6
    7
    import pandas as pd

    # 计算两个日期之间的差值
    start_date = pd.to_datetime('2024-09-01')
    end_date = pd.to_datetime('2024-09-05')
    delta = end_date - start_date
    print(delta)
    • category类型

    类型用于表示分类数据,通常用于有限集合中的数据类型,例如性别、颜色、产品类型等。这种类型的优点在于占用更少的内存,并且对分类数据的操作更快。

    1
    2
    3
    4
    5
    import pandas as pd

    # 创建一个category类型的Series
    categories = pd.Series(['apple', 'banana', 'apple', 'orange'], dtype='category')
    print(categories)

⭐️⭐️⭐️Pandas基本数据操作

读取、写入数据

获取数据方式有很多,包括去取CSV、MySQL、JSON、HDF5等等一些数据

MySQL建立连接需要install包

1
2
3
pip install pymysql==1.0.2 -i https://pypi.tuna.tsinghua.edu.cn/simple/
# 如果后边的代码运行提示找不到sqlalchemy的包,和pymysql一样进行安装即可
pip install sqlalchemy==2.0.0 -i https://pypi.tuna.tsinghua.edu.cn/simple/
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import pandas as pd

# CSV文件写入
df = pd.read_csv('data.csv',
sep=',', # 分隔符,默认为','
header=0, # 指定行号作为列名(默认为0)
index_col=0, # 指定列作为索引
encoding='utf-8', # 编码方式
dtype={'column1': str}, # 指定列数据类型
parse_dates=['date_column'], # 解析日期列
na_values=['NA', 'NULL']) # 指定缺失值标识

# CSV文件写入
data[:10].to_csv("./data/test.csv", columns=['open'])# 选取10行数据保存,便于观察数据

# 读取,查看结果
pd.read_csv("./data/test.csv")

# 读取Excel
df = pd.read_excel('data.xlsx',
sheet_name='Sheet1', # 指定工作表
engine='openpyxl') # 指定引擎

# 写入Excel
df.to_excel('output.xlsx',
sheet_name='Sheet1',
index=False) # 不写入索引列

# 读取JSON
df = pd.read_json('data.json',
orient='records', # JSON格式方向
lines=True) # 每行一个JSON对象

# 写入JSON
df.to_json('output.json',
orient='records',
indent=4) # 美化输出

# 这里是Pandas与MySQL建立链接需要导入的包
from sqlalchemy import create_engine

# 创建连接引擎
engine = create_engine('mysql+pymysql://user:password@host:port/database')

# 读取SQL数据
query = "SELECT * FROM table_name WHERE condition"
df = pd.read_sql(query, engine)

# 写入SQL
df.to_sql('table_name',
engine,
if_exists='append', # 如果表存在则追加
index=False)

# 读取HDF5
df = pd.read_hdf('data.h5',
key='dataset_name') # 指定数据集键名

# 写入HDF5
df.to_hdf('output.h5',
key='dataset_name',
mode='w') # 写入模式

# 读取Parquet
df = pd.read_parquet('data.parquet',
engine='pyarrow') # 指定引擎

# 写入Parquet
df.to_parquet('output.parquet',
compression='snappy') # 压缩方式

image-20250701201538884

⭐️⭐️⭐️索引操作

  • 直接索引方式(先列后行):
1
2
3
4
5
6
7
8
9
# 直接使用行列索引名字的方式(先列后行)
data['open']['2018-02-27']
23.53

# 不支持的操作
# 错误
data['2018-02-27']['open']
# 错误
data[:1, :2]
  • 结合loc或者iloc使用索引
    • loc后面是行列索引名称
    • 而iloc后面写index,正如名称带了i(index)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用loc:只能指定行列索引的名字
data.loc['2018-02-27':'2018-02-22', 'open']

2018-02-27 23.53
2018-02-26 22.80
2018-02-23 22.88
Name: open, dtype: float64

# 使用iloc可以通过索引的下标去获取
# 获取前3天数据,前5列的结果
data.iloc[:3, :5]

open high close low
2018-02-27 23.53 25.88 24.16 23.53
2018-02-26 22.80 23.78 23.53 22.80
2018-02-23 22.88 23.37 22.82 22.71

⭐️⭐️赋值操作

  • 会将close(收盘价)全部的值设置为1
1
2
3
4
# 直接修改原来的值
data['close'] = 1
# 或者
data.close = 1

⭐️⭐️排序操作

  • dataframe
    • 对象.sort_values()
    • 对象.sort_index()
  • series
    • 对象.sort_values()
    • 对象.sort_index()
  • DataFrame排序
1
2
3
4
5
6
7
8
# 按照开盘价大小进行排序 , 使用ascending指定按照大小排序
data.sort_values(by="open", ascending=True).head()

# 按照多个键进行排序
data.sort_values(by=['open', 'high'])

# 对索引进行排序
data.sort_index()
  • Series排序
1
2
3
4
5
data['p_change'].sort_values(ascending=True).head()

# 对索引进行排序
data['p_change'].sort_index().head()

⭐️⭐️DataFrame运算

算术运算

  • add(other)

比如进行数学运算加上具体的一个数字

1
2
3
4
5
6
7
data['open'].add(1)

2018-02-27 24.53
2018-02-26 23.80
2018-02-23 23.88
2018-02-22 23.25
2018-02-14 22.49
  • sub(other)

比如进行数学运算减去具体的一个数字

1
data['open'].sub(1)

⭐️⭐️逻辑运算

1
data[(data["open"] > 23) & (data["open"] < 24)].head()
  • query(expr)
    • expr:查询字符串

通过query使得刚才的过程更加方便简单

1
data.query("open<24 & open>23").head()
  • isin(values)

例如判断’open’是否为23.53和23.85

1
2
# 可以指定值进行一个判断,从而进行筛选操作
data[data["open"].isin([23.53, 23.85])]

统计运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 计算平均值、标准差、最大值、最小值
data.describe()

# 使用统计函数:0 代表列 求结果, 1 代表行求统计结果
data.max(0)

# 方差
data.var(0)

# 标准差
data.std(0)

# 中位数
df.median()

# 求出最大值的位置
data.idxmax(axis=0)

# 求出最小值的位置
data.idxmin(axis=0)

累计统计

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
###   累计统计  ###
# 排序之后,进行累计求和
data = data.sort_index()

stock_rise = data['p_change']
stock_rise.cumsum()

2015-03-02 2.62
2015-03-03 4.06
2015-03-04 5.63
2015-03-05 7.65
2015-03-06 16.16
2015-03-09 16.37
2015-03-10 18.75
2015-03-11 16.36
2015-03-12 15.03
2015-03-13 17.58
2015-03-16 20.34
2015-03-17 22.42
2015-03-18 23.28
2015-03-19 23.74
2015-03-20 23.48
2015-03-23 23.74


import matplotlib.pyplot as plt
# plot显示图形
stock_rise.cumsum().plot()
# 需要调用show,才能显示出结果(大概就是一个折线图)
plt.show()

apply自定义函数

1
2
3
4
5
6
# 定义一个对列,最大值-最小值的函数
data[['open', 'close']].apply(lambda x: x.max() - x.min(), axis=0)

open 22.74
close 22.85
dtype: float64

⭐️⭐️⭐️DataFrame数据的增删改查操作

增加

1
2
3
4
5
6
# 一列数据都是固定值
df3['new col 1'] = 33

# 新增列数据数量必须和行数相等
df3['new col 2'] = [1, 2, 3, 4, 5]
df3['new col 3'] = df3.year * 2
  • 使用assign函数增加
1
2
3
4
5
6
7
8
9
# 通过assign增加一列
df3.assign(c1=23)

# 通过assign增加多列
df3.assign(
c2 = ['x1','x2', 'x3', 'x4','x5'],
c3=df3.year * 2,
c4=lambda df:df.year * 100
)

删除与去重

1- df.drop删除行数据

1
2
3
df3.drop([0]) # 默认删除行
df3.drop([0, 2, 4]) # 可以删除多行
df3.GDP.drop([0, 2]) # 对series对象按索引删除

2- df.drop删除列数据1

  • df.drop默认删除指定索引值的行;如果添加参数axis=1,则删除指定列名的列
1
df3.drop(['new col 3'], axis=1)

3- 使用del删除指定的列

  • 注意区别:
    • del是直接永久删除原df中的列【慎重使用】
    • drop是返回删除后的df或seires,原df或seires没有被修改
1
2
3
del df3['new col 3']
df3
# 重复运行本段代码将会报错,因为df3中的指定列在第一次运行时就被删除了

4- Dataframe数据去重

1
2
3
4
5
# 添加一部分重复的数据
df4 = df2.append(df2).reset_index(drop=True)

# 实施去重操作
df4.drop_duplicates()

5- series去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
方式一:
df4.country.drop_duplicates()
# 返回结果如下
0 美国
1 英国
2 法国
3 中国
4 日本
Name: country, dtype: object


方式二:
df4.country.unique()
# 返回结果如下
array(['美国', '英国', '法国', '中国', '日本'], dtype=object)

修改

1
2
3
4
5
6
7
8
9
10
df5 = df5.assign(GDP=66) # 可以接收单变量或列表、数组

# series对象替换数据,返回的还是series对象,不会对原来的df造成修改
df6.year.replace(1960, 19600)

# 如果加上inplace=True参数,则会修改原始df
df6.country.replace('日本', '扶桑', inplace=True)

# df也可以直接调用replace函数,用法和s.replace用法一致,只是返回的是df对象
df6.replace(1960, 19600)

查询

  • 1- 从前从后取多行数据

head()

1
2
3
4
5
6
7
8
# 导包 
import pandas as pd
# 加载csv数据,指定gbk编码格式来读取文件,返回df
df = pd.read_csv('../数据集/1960-2019全球GDP数据.csv', encoding='gbk')

# 默认取前5行数据
df.head()
df.head(10) # 取前10行

tail()

1
2
3
# 默认取后5行数据
df.tail()
df2 = df.tail(15) # 倒数15行

2- 获取一列或多列数据

  • 获取一列数据df[col_name]等同于df.col_name
1
2
3
df2['country']
df2.country
# 注意!如果列名字符串中间有空格的,只能使用df['country']这种形式

获取多列数据df[[col_name1,col_name2,...]]

1
df2[['country', 'GDP']] # 返回新的df

3- 索引下标切片取行

  • df[start:stop:step]:

df[start:stop:step] == df[起始行下标:结束行下标:步长] , 遵循顾头不顾尾原则(包含起始行,不包含结束行),步长默认为1

1
2
3
4
df4 = df.head(10) # 取原df前10行数据作为df4,默认自增索引由0到9
df4[0:3] # 取前3行
df4[:5:2] # 取前5行,步长为2
df4[1::3] # 取第2行到最后所有行,步长为3

4- 查询函数获取子集: df.query()

  • df.query(判断表达式)可以依据判断表达式返回的符合条件的df子集
  • df[布尔值向量]效果相同
  • 特别注意df.query()中传入的字符串格式
1
2
df3.query('country=="帕劳"')
df3[df3['country']=='帕劳']
1
2
3
# 查询中国, 美国 日本 三国 2015年至2019年的数据
df.query('country=="中国" or country=="日本" or country=="美国"').query('year in ["2015", "2016", "2017", "2018", "2019"]')
df.query('(country=="中国" or country=="日本" or country=="美国") and year in ["2015", "2016", "2017", "2018", "2019"]')

5- 排序函数

  • sort_values函数: 按照指定的一列或多列的值进行排序
1
2
3
4
5
6
# 按GDP列的数值由小到大进行排序
df2.sort_values(['GDP'])
# 按GDP列的数值由大到小进行排序
df2.sort_values(['GDP'], ascending=False) # 倒序, ascending默认为True
# 先对year年份进行由小到大排序,再对GDP由小到大排序
df2.sort_values(['year', 'GDP'])

rank函数:

  • rank函数用法:DataFrame.rank()Series.rank()
  • rank函数返回值:以Series或者DataFrame的类型返回数据的排名(哪个类型调用返回哪个类型)
  • rank函数包含有6个参数:
  • axis:设置沿着哪个轴计算排名(0或者1),默认为0按纵轴计算排名
  • numeric_only:是否仅仅计算数字型的columns,默认为False
  • na_option :NaN值是否参与排序及如何排序,固定参数:keep top bottom
  • keep: NaN值保留原有位置
  • top: NaN值全部放在前边
  • bottom: NaN值全部放在最后
  • ascending:设定升序排还是降序排,默认True升序
  • pct:是否以排名的百分比显示排名(所有排名与最大排名的百分比),默认False
  • method:排名评分的计算方式,固定值参数,常用固定值如下:
  • average : 默认值,排名评分不连续;数值相同的评分一致,都为平均值
  • min : 排名评分不连续;数值相同的评分一致,都为最小值
  • max : 排名评分不连续;数值相同的评分一致,都为最大值
  • dense : 排名评分是连续的;数值相同的评分一致
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
df7 = pd.DataFrame({
'姓名':['小明', '小美', '小强', '小兰'],
'成绩':[100, 90, 90, 80]
})

df7.rank() # 等价于df7.rank(method='average', ascending=True)
姓名 成绩
0 4.0 4.0
1 2.5 2.5
2 2.5 2.5
3 1.0 1.0

df7['成绩排名'] = df7.成绩.rank(method='min', ascending=False)
姓名 成绩 成绩排名
0 小明 100 1.0
1 小美 90 2.0
2 小强 90 2.0
3 小兰 80 4.0

df7['成绩排名'] = df7.成绩.rank(method='max', ascending=False)
姓名 成绩 成绩排名
0 小明 100 1.0
1 小美 90 3.0
2 小强 90 3.0
3 小兰 80 4.0

df7['成绩排名'] = df7.成绩.rank(method='dense', ascending=False)
姓名 成绩 成绩排名
0 小明 100 1.0
1 小美 90 2.0
2 小强 90 2.0
3 小兰 80 3.0

⭐️⭐️⭐️缺失值处理

缺失值为NAN

删除

1
2
3
4
5
# 不修改原数据
movie.dropna()

# 可以定义新的变量接受或者用原来的变量名
data = movie.dropna()

替换/填充

1
2
3
4
5
6
7
8
9
# 替换存在缺失值的样本的两列
# 替换填充平均值,中位数
movie['Revenue (Millions)'].fillna(movie['Revenue (Millions)'].mean(), inplace=True)

# 替换所有
for i in movie.columns:
if np.all(pd.notnull(movie[i])) == False:
print(i)
movie[i].fillna(movie[i].mean(), inplace=True)

缺失值是特殊字符

1
2
3
4
5
6
7
wis = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data")

# 把一些其它值标记的缺失值,替换成np.nan
wis = wis.replace(to_replace='?', value=np.nan)

# 删除
wis = wis.dropna()

以上数据在读取时,可能会报如下错误:

1
URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)>

解决办法:

1
2
3
# 全局取消证书验证
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

数据合并

  • pd.concat实现数据的合并 => union
  • pd.merge实现数据的合并 => join

concat

pd.concat([data1, data2], axis=1)

  • 按照行或列进行合并,axis=0为列索引,axis=1为行索引
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
46
47
48
49
50
51
import pandas as pd

# 创建两个DataFrame
df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2'],
'B': ['B0', 'B1', 'B2']},
index=[0, 1, 2])

df2 = pd.DataFrame({'A': ['A3', 'A4', 'A5'],
'B': ['B3', 'B4', 'B5']},
index=[3, 4, 5])

# 垂直连接(默认axis=0)
result = pd.concat([df1, df2])
print(result)

A B
0 A0 B0
1 A1 B1
2 A2 B2
3 A3 B3
4 A4 B4
5 A5 B5

df3 = pd.DataFrame({'C': ['C0', 'C1', 'C2'],
'D': ['D0', 'D1', 'D2']},
index=[0, 1, 2])

# 水平连接
result = pd.concat([df1, df3], axis=1)
print(result)

A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2

df4 = pd.DataFrame({'B': ['B2', 'B3', 'B4'],
'D': ['D2', 'D3', 'D4']},
index=[2, 3, 4])

# 连接不同索引的DataFrame
result = pd.concat([df1, df4], axis=0, sort=False)
print(result)

A B D
0 A0 B0 NaN
1 A1 B1 NaN
2 A2 B2 NaN
2 NaN B2 D2
3 NaN B3 D3
4 NaN B4 D4

merge

pd.merge(left, right, how=’inner’, on=None)

  • 可以指定按照两组数据的共同键值对合并或者左右各自
  • left: DataFrame
  • right: 另一个DataFrame
  • on: 指定的共同键
  • how:按照什么方式连接

看这里:https://liamjohnson-w.github.io/2024/08/12/2024.08.12/#%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86-%E5%90%88%E5%B9%B6

数据分组

分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 导包 加载数据集
import pandas as pd
df = pd.read_csv('../数据集/uniqlo.csv')

# 基于顾客性别、不同城市分组
gs2 = df.groupby(['gender_group', 'city'])
gs2
# 返回结果如下
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001B1DB24F1F0>

gs2.first() # 取出每组第一条数据
gs2.last() # 取出每组最后一条数据

# 按分组依据获取其中一组
gs2.get_group(('Female', '线上'))

分组聚合

1
2
3
4
5
# 按城市和线上线下划分,分别计算销售金额的平均值、成本的总和
df.groupby(['city', 'channel']).agg({
'revenue':'mean',
'unit_cost':'sum'
})

分组过滤

1
2
3
# 按城市分组,查询每组销售金额平均值大于200的全部数据
df.groupby(['city']).filter(lambda s: s['revenue'].mean() > 200)
df.groupby(['city'])['revenue'].filter(lambda s: s.mean() > 200)

交叉表与透视表

  • 交叉表:

    交叉表用于计算一列数据对于另外一列数据的分组个数(用于统计分组频率的特殊透视表)

    • pd.crosstab(value1, value2)
  • 透视表:

    透视表是将原有的DataFrame的列分别作为行索引和列索引,然后对指定的列应用聚集函数

    • data.pivot_table()
    • DataFrame.pivot_table([], index=[])

交叉表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd

# 创建一个示例数据集
data = {
'性别': ['男', '女', '男', '女', '男', '女', '女', '男'],
'购买': ['是', '否', '是', '是', '否', '否', '是', '否']
}

df = pd.DataFrame(data)

# 创建交叉表
crosstab = pd.crosstab(df['性别'], df['购买'])
print(crosstab)

购买 否 是
性别
2 2
2 2

透视表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd

data = {
'性别': ['男', '女', '男', '女', '男', '女'],
'购买': ['是', '否', '是', '是', '否', '否'],
'金额': [100, 150, 200, 130, 160, 120]
}
df = pd.DataFrame(data)

# 创建透视表
pivot_table = pd.pivot_table(df, values='金额', index='性别', columns='购买', aggfunc='mean')
print(pivot_table)

购买 否 是
性别
135.0 130.0
180.0 150.0

案例分析

数据准备

  • 准备两列数据,星期数据以及涨跌幅是好是坏数据
  • 进行交叉表计算
1
2
3
4
5
6
7
8
9
10
# 寻找星期几跟股票张得的关系
# 1、先把对应的日期找到星期几
date = pd.to_datetime(data.index).weekday
data['week'] = date

# 2、假如把p_change按照大小去分个类0为界限
data['posi_neg'] = np.where(data['p_change'] > 0, 1, 0)

# 通过交叉表找寻两列数据的关系
count = pd.crosstab(data['week'], data['posi_neg'])

但是我们看到count只是每个星期日子的好坏天数,并没有得到比例,该怎么去做?

  • 对于每个星期一等的总天数求和,运用除法运算求出比例
1
2
3
4
5
# 算数运算,先求和
sum = count.sum(axis=1).astype(np.float32)

# 进行相除操作,得出比例
pro = count.div(sum, axis=0)

查看效果

使用plot画出这个比例,使用stacked的柱状图

1
2
pro.plot(kind='bar', stacked=True)
plt.show()

使用pivot_table(透视表)实现

使用透视表,刚才的过程更加简单

1
2
# 通过透视表,将整个过程变成更简单一些
data.pivot_table(['posi_neg'], index='week')

⭐️⭐️Pandas综合案例⭐️⭐️

两个案例对接实际业务场景,算是Pandas的入门经典案例了。

题目

image-20250702201844580

GDP案例

源数据长这样

image-20250702201718788

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
import pandas as pd
import numpy as np

df = pd.read_csv("../Files/1960-2019全球GDP数据.csv", encoding='gbk')
df.info()

# 1.筛选出 2000 年之后(含 2000 年)的数据,计算这些数据中各国家 GDP 的总和。
df[df['year'] >= 2000].groupby('country').agg({'GDP':'sum'})

# 2.按国家分组,过滤出 GDP 数据标准差大于 1000000 的国家,显示这些国家的名称和 GDP 标准差。
# 方法1:先分组计算标准差,再筛选
std_df = df.groupby('country')['GDP'].std().reset_index()
result = std_df[std_df['GDP'] > 1000000]

# 方法2:使用agg和filter的组合
result = df.groupby('country')['GDP'].agg(['std']).reset_index()
result = result[result['std'] > 1000000]

# 3.计算每个国家 GDP 占全球 GDP 总和的比例,按年份分组,找出每年占比最高的前三个国家。
# 计算每个国家每年GDP占全球GDP的比例
df['gdp_ratio'] = df.groupby('year')['GDP'].apply(lambda x: x / x.sum())

# 按年份分组,找出每年GDP占比最高的前3个国家
top3_countries = df.groupby('year').apply(
lambda x: x.nlargest(3, 'gdp_ratio')[['country', 'gdp_ratio']]
).reset_index(drop=True)

# 4.把国家名称中包含特定字符(如 “国”)的国家数据提取出来,按 GDP 降序排序,保存为新的 CSV 文件。
# 提取国家名称中包含"国"的数据
filtered_df = df[df['country'].str.contains('国')]

# 按GDP降序排序
sorted_df = filtered_df.sort_values('GDP', ascending=False)

# 保存为新的CSV文件
sorted_df.to_csv('countries_with_国.csv', index=False, encoding='utf-8-sig')

优衣库案例

算是很经典的案例了吧,网上有很多demo。

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
import pandas as pd
import numpy as np

df = pd.read_csv("../Files/uniqlo.csv", encoding='gbk')
df.info()

# 1.读取文件,筛选出价格在 50 - 100 元之间且销量大于 100 的商品,按销量降序排序后保存为新的 CSV 文件。
# 筛选条件
filtered_df = df[(df['价格'] >= 50) & (df['价格'] <= 100) & (df['销量'] > 100)]=
# 按销量降序排序
sorted_df = filtered_df.sort_values('销量', ascending=False)
# 保存为新CSV文件
sorted_df.to_csv('filtered_uniqlo.csv', index=False, encoding='utf-8-sig')

# 2.按商品类别分组,过滤出平均价格低于 30 元的类别,展示这些类别的名称和平均价格。
# 按类别分组计算平均价格
category_avg = df.groupby('商品类别')['价格'].mean().reset_index()
# 筛选平均价格低于30元的类别
low_price_categories = category_avg[category_avg['价格'] < 30]

# 3.读取文件后,去除销量为 0 的商品记录,然后计算剩余商品的总销售额。
# 去除销量为0的记录
non_zero_sales = df[df['销量'] > 0]
# 计算销售额(价格×销量)
non_zero_sales['销售额'] = non_zero_sales['价格'] * non_zero_sales['销量']
# 计算总销售额
total_sales = non_zero_sales['销售额'].sum()

⭐️RFM案例⭐️

RFM是什么?

  • Recency:最近一次购买时间

  • Frequency:购买频率

  • Monetary:购买金额

需求说明:

image-20250704192745803

原始数据长这样:

image-20250704195013659

注意事项

  • 如果Excel有多个Sheet,则pd.read_excel会生成字典:{‘Sheet名称’: ‘Sheet对应的DataFrame’}
  • 多个DataFrame进行Concat会上下拼接(类似Union all)但注意设置ignore_redex=False
  • pd[‘提交日期’].dt.year会获取日期的年(类似还有:dt.month、dt.days)
  • 重命名DataFrame中的Column:
    • df.columns = [‘year’, ‘会员ID’, ‘r’, ‘f’, ‘m’]
  • pd.cut(column,bins列表,labels=[3, 2, 1])会根据bins划分的区间对DataFrame中的column列进行打标,标签为labels中对应的数据,如:
    • df[‘r_lable’] = pd.cut(df[‘r’], bins=r_bins, labels=[3, 2, 1])

代码

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import pandas as pd
import numpy as np

sheet_names = ['2015', '2016', '2017', '2018', '会员等级']

sheet_dict = pd.read_excel('/Users/lifuyao/PycharmProjects/pythonProject/Files/sales1.xls', sheet_name=sheet_names)

# 浏览数据信息
from pyecharts.charts import Bar3D

# 数据预处理
for i in sheet_names[:-1]:
sheet_dict[i] = sheet_dict[i].dropna()
sheet_dict[i] = sheet_dict[i].query('订单金额 > 1')
sheet_dict[i] = sheet_dict[i].assign(max_year_date = sheet_dict[i]['提交日期'].max())
print(sheet_dict[i].info())

pd_concat = pd.concat(list(sheet_dict.values())[:-1], ignore_index=True)

pd_concat['year'] = pd_concat['提交日期'].dt.year

pd_concat['time_delate'] = (pd_concat['max_year_date'] - pd_concat['提交日期']).dt.days

df = pd_concat.groupby(['year', '会员ID'], as_index=False).agg({
'time_delate': 'min',
'订单号': 'count',
'订单金额': 'sum'
})

df.columns = ['year', '会员ID', 'r', 'f', 'm']


# 划分区间
# 系统自动划分区间
# pd.cut(df['r'], bins=3).unique
# [(-0.365, 121.667] < (121.667, 243.333] < (243.333, 365.0]]

# 给定区间数,用describe函数获得的25%,50%,75%中位数划分
df.describe()

r_bins = [-1, 79, 255,365]
f_bins = [0, 2, 5, 130]
m_bins = [1, 69, 1199, 206300]

pd.cut(df['r'], bins=r_bins).unique()

df['r_lable'] = pd.cut(df['r'], bins=r_bins, labels=[3, 2, 1])
df['f_lable'] = pd.cut(df['f'], bins=f_bins, labels=[1, 2, 3])
df['m_lable'] = pd.cut(df['m'], bins=m_bins, labels=[1, 2, 3])


# 拼接评分
df['r_lable'] = df['r_lable'].astype(str)
df['f_lable'] = df['f_lable'].astype(str)
df['m_lable'] = df['m_lable'].astype(str)

df['rfm_group'] = df['r_lable'] + df['f_lable'] + df['m_lable']

df.head()
# 存储为Excel
df.to_excel("./res.xlsx", index = False)

from sqlalchemy import create_engine
# 存储到Mysql中(1,创建引擎 2,写入数据
engine= create_engine('mysql+pymysql://root:root@localhost:3306/rfm_db?charset=utf8')

#2、具体导出数据到MySQL表中
df.to_sql('sales_rfm_score',engine,index=False,if_exists='replace')

# pyecharts 交付 rfm_group(分组结果评分), year(统计年份), number(评分个数)

# 7-3 pyecharts 交付

from pyecharts.charts import Bar3D
from pyecharts.commons.utils import JsCode
import pyecharts.options as opts
import os
# 5.1 准备可视化的数据, 即: rfm_group(分组结果评分), year(统计年份), number(评分个数)
display_data = df.groupby(['rfm_group', 'year'], as_index=False).agg({'会员ID': 'count'})
display_data
# 5.2 修改列名.
display_data.columns = ['rfm_group', 'year', 'number']
# 细节: 把number列的类型 -> int类型.
display_data['number'] = display_data['number'].astype(int)
display_data

# 5.3 绘制图形【这里不需要敲,直接复制即可】
# 颜色池
range_color = ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf',
'#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']

range_max = int(display_data['number'].max())
c = (
Bar3D()#设置了一个3D柱形图对象
.add(
"",#图例
[d.tolist() for d in display_data.values],#数据
xaxis3d_opts=opts.Axis3DOpts(type_="category", name='分组名称'),#x轴数据类型,名称,rfm_group
yaxis3d_opts=opts.Axis3DOpts(type_="category", name='年份'),#y轴数据类型,名称,year
zaxis3d_opts=opts.Axis3DOpts(type_="value", name='会员数量'),#z轴数据类型,名称,number
)
.set_global_opts( # 全局设置
visualmap_opts=opts.VisualMapOpts(max_=range_max, range_color=range_color), #设置颜色,及不同取值对应的颜色
title_opts=opts.TitleOpts(title="RFM分组结果"),#设置标题
)
)
c.render() # 数据保存到本地的网页中.

代码带详细注释

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import  time  #:用来记录插入数据库时的当前日期
import numpy #:用来做基本数据处理等
import pandas as pd
from sqlalchemy import create_engine
# pyecharts:展示3D柱形图 pip install pyecharts
from pyecharts.charts import Bar3D
from pyecharts.commons.utils import JsCode
import pyecharts.options as opts
import os
os.chdir(r"E:\workspace\")

#2、加载数据
#2-1、定义列表 记录excel每一个sheet页名字
sheet_names=['2015','2016','2017','2018','会员等级']
#2-2 读取excel里面的数据
#参1: excel文件路径
#参2:excel文件里面的sheet页的名字
#详解: read_excel 读取的数据 是字典类型-》{'2015':df对象,'2016':df对象,'2017':df对象...}
sheet_dict=pd.read_excel('./data/sales.xlsx',sheet_name=sheet_names)
sheet_dict

#2-7 查看字典中的每个df对象(即:每张sheet表)的基本信息和统计信息
for i in sheet_names:
print(i) #i ->每个sheet表名
#打印基本信息和基本统计信息
print(sheet_dict[i].info())
print(sheet_dict[i].describe())

#3- 数据的预处理
#3-1 需要预处理动作
# 1:删除缺失值
# 2:过滤出金额>1订单
# 3:固定时间节点,以每年的最后1天作为当年分析节点

#3-2 遍历 ,获取每张表的数据(除了最后一张)
for i in sheet_names[:-1]: # sheet_names[0:4]:
# print(i)
#3-2-1:删除缺失值
sheet_dict[i]=sheet_dict[i].dropna()
#3-2-2:过滤出金额>1的值
#sheet_dict[i]['订单金额']>1(dataframe)
sheet_dict[i]=sheet_dict[i][sheet_dict[i]['订单金额']>1]
#3-2-3:以每年的最后1天作为当年分析节点
sheet_dict[i]['max_year_date']=sheet_dict[i]['提交日期'].max()

# 3-3 查看处理后的数据
for i in sheet_names:
print(i)
print(sheet_dict[i].info())
print(sheet_dict[i].describe())

#3-4 把上述四张表(对应的4个df对象)合并为1个df对象
# sheet_dict['2015']
# sheet_dict['2016']
# ...
# 目标:合并2015、2016、2017、2018 怎么办?
# 分析:sheet_dict 字典格式 {'2015':df对象,'2016':df对象,'2017':df对象}
# 所以:sheet_dict.values 但是dict_values类型 ,concat需要列表
# 所以:list(sheet_dict.values()) 如果这样的话,会员表也合并进去了,list[:-1]去掉会员表
#技术解决方案:concat()
df_concat=pd.concat(list(sheet_dict.values())[:-1],ignore_index=True) #合并后需要重置索引

#3-5:为了好区分:df_concat对象新增列 year列
df_concat['year']=df_concat['提交日期'].dt.year
df_concat

#3-6 给表新增1列 ,data_interval 表示本订单购买时间 距离 统计 时间节点的差值
df_concat['data_interval']=df_concat['max_year_date']-df_concat['提交日期']
# df_concat.info()
#3-7 处理间隔时间中 days
df_concat['data_interval']=df_concat['data_interval'].dt.days
df_concat

#as_index=False 不要把分组字段设置索引
#4、计算R:最近购买时间 F:购买次数 M:购买金额
#4-1 基于year 和会员ID分组 ,统计 RFM的基本数据
rfm_gb=df_concat.groupby(['year','会员ID'],as_index=False).agg(
{
'data_interval':'min',
'订单号':'count',
'订单金额':'sum'
}
)
rfm_gb
#4-2 :修改列名
rfm_gb.columns=['year','会员ID','r','f','m']
rfm_gb

#5、 分析 RFM 值的分布情况
rfm_gb.iloc[:,2:].describe().T

#5-1 划分区间 分别给出 RFM评分:
# r:最近一次购买时间 越小分越高
# f: 购买次数 越大分越高
# m: 购买金额 越大分越高

#思路1: 我们给定区间数,用系统根据数据自动划分区间
# pd.cut(rfm_gb['r'],bins=3).unique()
# [(-0.365, 121.667], (243.333, 365.0],(121.667, 243.333]] #包右不包左
# 不包括 包括 不包括 包括 不包括 包括
# 3 2 1

pd.cut(rfm_gb['f'],bins=3).unique()
# [(-204.75, 68751.6], (68751.6, 137501.7], (137501.7, 206251.8]]
# 1 2 3

#思路2:我们手动的指定区间范围 ,由系统自动划分区间数据
# 提问:我们想划分几个区间 ?3个区间 ,需要4个值 ,就可以划分三个区间
r_bins=[-1,79,255,365]
f_bins=[0,2,5,130]
m_bins=[1,69,1199,206300]
pd.cut(rfm_gb['r'],bins=r_bins).unique()

#思路3: 基于我们手动指定区间范围 ,给出每个范围评分(三分法 : 低(1)中(2)高(3))
rfm_gb['r_label']=pd.cut(rfm_gb['r'],bins=r_bins,labels=[3,2,1]) # r值(最近购买间隔)越小,分越高
rfm_gb['f_label']=pd.cut(rfm_gb['f'],bins=f_bins,labels=[1,2,3]) #f值(购买次数)越大,分越高
rfm_gb['m_label']=pd.cut(rfm_gb['m'],bins=m_bins,labels=[1,2,3]) #m值(购买金额)越大,分越高
rfm_gb

# 思路3_实际开发写法 (完整版) 因为后期可能改为4分、5分法 ,这里写死了,不太好,可以灵活应对
rfm_gb['r_label']=pd.cut(rfm_gb['r'],bins=r_bins,labels=[i for i in range(len(r_bins)-1,0,-1)])
rfm_gb['f_label']=pd.cut(rfm_gb['f'],bins=f_bins,labels=[i for i in range(1,len(f_bins))])
rfm_gb['m_label']=pd.cut(rfm_gb['m'],bins=m_bins,labels=[i for i in range(1,len(m_bins))])
rfm_gb


#6、 RFM计算结果处理
# #6-1 处理方式有两个
# 6-1-1:拼接 因为我们是面向销售人员,他们需要分析用户的不同维度打分,制定不用的营销和运营方案 ,方便分析客户
# 6-1-2:累加

#第一步:类型转换 把 r_babel f_label m_label 分类类型 转换为字符串类型
rfm_gb['r_label']=rfm_gb['r_label'].astype(str)
rfm_gb['f_label']=rfm_gb['f_label'].astype(str)
rfm_gb['m_label']=rfm_gb['m_label'].astype(str)

#第二步:拼接评分
rfm_gb['rfm_group']=rfm_gb['r_label']+rfm_gb['f_label']+rfm_gb['m_label']
rfm_gb

#7、结果数据保存
#7-1 保存到excel
rfm_gb.to_excel('./data/sales_rfm_score1.xlsx',index=False)

#7-2 保存mysql表中

#1、创建引擎对象
# mysql+mysqldb://scott:tiger@hostname/dbname",pool_recycle=3600
engine=create_engine('mysql+pymysql://root:root@localhost:3306/rfm_db?charset=utf8')

#2、具体导出数据到MySQL表中
rfm_gb.to_sql('sales_rfm_score',engine,index=False,if_exists='replace')


# 7-3 pyecharts 交付
# 5.1 准备可视化的数据, 即: rfm_group(分组结果评分), year(统计年份), number(评分个数)
display_data = rfm_gb.groupby(['rfm_group', 'year'], as_index=False).agg({'会员ID': 'count'})
display_data
# 5.2 修改列名.
display_data.columns = ['rfm_group', 'year', 'number']
# 细节: 把number列的类型 -> int类型.
display_data['number'] = display_data['number'].astype(int)
display_data

# 5.3 绘制图形【这里不需要敲,直接复制即可】
# 颜色池
range_color = ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf',
'#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']

range_max = int(display_data['number'].max())
c = (
Bar3D()#设置了一个3D柱形图对象
.add(
"",#图例
[d.tolist() for d in display_data.values],#数据
xaxis3d_opts=opts.Axis3DOpts(type_="category", name='分组名称'),#x轴数据类型,名称,rfm_group
yaxis3d_opts=opts.Axis3DOpts(type_="category", name='年份'),#y轴数据类型,名称,year
zaxis3d_opts=opts.Axis3DOpts(type_="value", name='会员数量'),#z轴数据类型,名称,number
)
.set_global_opts( # 全局设置
visualmap_opts=opts.VisualMapOpts(max_=range_max, range_color=range_color), #设置颜色,及不同取值对应的颜色
title_opts=opts.TitleOpts(title="RFM分组结果"),#设置标题
)
)
c.render() # 数据保存到本地的网页中.

引言

为什么使用Pandas:

  • 以Numpy为基础,借力Numpy模块在计算方面性能高的优势
  • 基于matplotlib,能够简便的画图
  • 独特的数据结构
  • 读取文件方便
  • 封装了Matplotlib、Numpy的画图和计算

举例论证

  • 增强图表可读性

    • 在numpy当中创建学生成绩表样式:
  • 返回结果:

1
2
3
4
5
6
7
8
9
10
array([[92, 55, 78, 50, 50],
[71, 76, 50, 48, 96],
[45, 84, 78, 51, 68],
[81, 91, 56, 54, 76],
[86, 66, 77, 67, 95],
[46, 86, 56, 61, 99],
[46, 95, 44, 46, 56],
[80, 50, 45, 65, 57],
[41, 93, 90, 41, 97],
[65, 83, 57, 57, 40]])

如果数据展示为这样,可读性就会更友好:

image-20240812222353841

Pandas数据结构

Pandas中一共有三种数据类型,分别为:Series、DataFrame和MultiIndex(老版本中叫Panel )。

其中Series是一维数据类型,DataFrame是二维的表格型数据类型,MultiIndex是三维的数据类型。

Series

Pandas中的Series类似于线性代数中的列向量(补充:也可以是行向量)。

  • pd.Series(data, index, dtype):创建一个列向量
    • 注意:data可以为数组,也可以为字典
    • 同时,在如果是一个数组的时候可以指定索引,也就是index前面的data每一个数字的索引位置
  • Series.index:获取该列向量的全部索引index
  • Series.values:获取该列向量所有的数值data
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
import pandas as pd
import numpy as np

"""
创建Series
"""
# 从Pandas开始,使用正规的打印输出
print(pd.Series(data=None, index=None, dtype=None))
# 不指定索引和数据类型,直接创建一个series
print(pd.Series(np.arange(10)))
# np.arange(10) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

# 创建Series时指定索引和数据类型
print(pd.Series([6.7,5.6,3,10,2], [1,2,3,4,5]))

# 通过字典数据创建
print(pd.Series({'red':100, 'blue':200, 'green': 500, 'yellow':1000}))


"""
Series的属性:index, values(类似于字典中的键值对的键和值)
"""
color_series = pd.Series({'red':100, 'blue':200, 'green': 500, 'yellow':1000})
print(color_series.index)
print(color_series.values)
# 使用索引来获取数据
print(color_series['red'])

DataFrame

DataFrame相当于线性代数中的矩阵。

除了注意DataFrame中的数据,DataFrame中的行列索引也是比较重要的东西。

怕弄混,这里搞个图片标记一下。

image-20240823223945178

  • pd.DataFrame(data, index, colunms):创建一个DataFrame
    • 注意:传入的Data必须为二维数组
  • pd.DataFrame(df, column, index):给一个已知的DataFrame重新添加索引
    • 注意:column是横的,index是竖的就行了
  • df.shape:输出当前DataFrame(df)的形状
    • 下面的data就是DataFrame
  • data.index : 行索引列表
  • data.columns : 列索引列表
  • data.values : 取其中array的值,类似于numpy的原始数据类型
  • data.T : 将data进行转置(10, 5) -> (5, 10)
  • data.head(5): 只显示data前五行的内容
  • data.tail(5) : 只显示data后五行的内容
  • df.index = [列表]:修改列索引(也就是修改竖的的那个索引
  • df.reset_index(drop=True/False):重新设置新的索引
  • df.set_index(‘month’):设置月份为当前DataFrame的索引
    • 注意:一个DataFrame可以设置多个字段为索引,但是需要用[]列表的形式表示
    • 如:df.set_index([‘year’, ‘month’])
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import numpy as np
import pandas as pd

"""
创建DataFrame
"""
print(pd.DataFrame(data=None, index=None, columns=None))
print(np.random.randn(2,3))

# np.random.randn是numpy的一个函数,从标准正态分布函数中随机生成随机数并指定形状为2行3列
unipd = pd.DataFrame(np.random.randn(2,3))

# 普通的numpy结构和DataFrame对比
score = np.random.randint(40,100,(10,5))
print("生成10行5列的40-100之间的随机数为",score)

# 上面直接生成可读性比较差,可以使用pandas的DataFrame让数据更有意义
score_df = pd.DataFrame(score)
print(score_df)

# 由于上面的行列标签都是从0开始的数字,可以手动构造索引序列
# 构造列索引序列
subject = ["语文", "数学", "英语", "政治", "体育"]

# 构造行索引序列
stu = ['同学'+ str(i) for i in range(score_df.shape[0])]

# 添加行,列索引
data = pd.DataFrame(score, columns=subject, index=stu)
print(data)
# 语文 数学 英语 政治 体育
# 同学0 83 90 71 44 57
# 同学1 79 69 84 81 84
# 同学2 58 67 64 77 93
# .....

# DataFrame的属性
data.shape # (10, 5)
print("DataFrame的形状的行数为:", score_df.shape[0]) # 10行,所以上面等价与 for i in range(10)
data.index # 行索引列表
data.columns # 列索引列表
data.values # 取其中array的值,类似于numpy的原始数据类型
data.T # 将data进行转置(10, 5) -> (5, 10)
data.head(5) # 只显示data前五行的内容
data.tail(5) # 只显示data后五行的内容

# 修改行列索引的值
stu = ["天朝学生_" + str(i) for i in range(score_df.shape[0])]
data.index = stu
print(data)

# 重新设置索引 reset_index(drop=False)
data.reset_index(drop=True)

# 设置新的索引
df = pd.DataFrame({'month': [1, 4, 7, 10],
'year': [2012, 2014, 2013, 2014],
'sale':[55, 40, 84, 31]})
print(df)

# 以月份设置新的索引
df.set_index('month')

# 以年份和月份设置多个索引
df.set_index(['year', 'month'])

MultiIndex与Panel

MultiIndex是三维的数据类型;

多级索引(也称层次化索引)是pandas的重要功能,可以在Series、DataFrame对象上拥有2个以及2个以上的索引。

其实简单理解就是多级索引,想象一张成绩表,张三学生下面有很多的学科,每一门学科都有各自的成绩。

再比如将数据分为1和2组,下面是pandas跑出来的结果:

image-20240823215403703

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
import pandas as pd
import numpy as np

# MultiIndex是三维的数据类型;
# 多级索引(也称层次化索引)是pandas的重要功能,可以在Series、DataFrame对象上拥有2个以及2个以上的索引。
df = pd.DataFrame({'month': [1, 4, 7, 10],
'year': [2012, 2014, 2013, 2014],
'sale':[55, 40, 84, 31]})
df.index

# multiIndex 的创建
arrays = [[1, 1, 2, 2], ['red', 'blue', 'red', 'blue']]
mi_index = pd.MultiIndex.from_arrays(arrays, names=('number', 'color'))
mi_index
mi_df = pd.DataFrame(np.random.normal(size=(4,4)),index=mi_index) # 创建四行四列的数组,行索引指定为mi_index
mi_df

# Panel: 用于存储三维数组
p = pd.Panel(data=np.arange(24).reshape(4,3,2),
items=list('ABCD'),
major_axis=pd.date_range('20130101', periods=3),
minor_axis=['first', 'second'])
# 查看panel的数据
p
# <class 'pandas.core.panel.Panel'>
# Dimensions: 4 (items) x 3 (major_axis) x 2 (minor_axis)
# Items axis: A to D
# Major_axis axis: 2013-01-01 00:00:00 to 2013-01-03 00:00:00
# Minor_axis axis: first to second

# 查看panel的数据
p[:,:,"first"]
# A B C D
# 2013-01-01 0 6 12 18
# 2013-01-02 2 8 14 20
# 2013-01-03 4 10 16 22

p["B",:,:]
# first second
# 2013-01-01 6 7
# 2013-01-02 8 9
# 2013-01-03 10 11

Pandas的索引、切片基操

首先引入文件操作,Pandas支持读取的文件为CSV、SQL、XLS、JSON、HDF5。

注:最常用的HDF5和CSV文件

1
2
3
4
5
# 读取文件
data = pd.read_csv("./data/stock_day.csv")

# 删除一些列,让数据更简单些,再去做后面的操作
data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)

索引操作

  • df[‘字段名称’]:取出某个DataFrame中的某一列
  • df[‘列索引’][‘行索引’]:精确找到列索引+行索引所确定的某个值
  • df.drop([‘字段1’, ‘字段2’….]):裁剪DataFrame,使列表里面的字段全部不显示or生成新的DataFrame
  • df.loc[行,列]:这个是先行后列,也都是字段名称,跟上面第二个相反
  • df.iloc[行索引下标,列索引下标]:这个iloc跟loc最大的区别就是iloc可以通过索引下标操作
  • df.ix[行,列索引下标]:这个ix’就是将上面loc和iloc混在一块了,然后导致后面也用的比较少,就过时了
  • 最重要的还是loc和iloc的组合操作
  • df.index[下标切片]:获取DataFrame指定下标的索引,返回的是具体数字
  • df.columns().get_indexer([字段1,字段2]):获取字段的列下标,返回的是下标
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
import pandas as pd
import numpy as np

# 读取数据
data = pd.read_csv('./stock_day.csv')
# print(data)
# 删除一些列,让数据变得更简单一些,再去做后面的操作
data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)

# 使用行列索引操作数据(先列后行)
print(data['open']['2018-02-27']) # 23.53

# 使用loc和iloc进行行列索引操作(这个跟直接使用行列索引的顺序刚好相反)
data.loc['2018-02-27': '2018-02-22', 'open']
# 2018-02-27 23.53
# 2018-02-26 22.80
# 2018-02-23 22.88
# 2018-02-22 22.25
# Name: open, dtype: float64

# 使用iloc可以使用数组下标去操作-获取前三天,前五列的数据
data.iloc[:3, :5]
# open high close low volume
# 2018-02-27 23.53 25.88 24.16 23.53 95578.03
# 2018-02-26 22.80 23.78 23.53 22.80 60985.11
# 2018-02-23 22.88 23.37 22.82 22.71 52914.01

# 使用ix组合索引(现在已经过时了)
# Warning:Starting in 0.20.0, the .ix indexer is deprecated, in favor of the more strict .iloc and .loc indexers.
# 使用ix进行下标和名称组合做引
data.ix[0:4, ['open', 'close', 'high', 'low']]
# open close high low
# 2018-02-27 23.53 24.16 25.88 23.53
# 2018-02-26 22.80 23.53 23.78 22.80
# 2018-02-23 22.88 22.82 23.37 22.71
# 2018-02-22 22.25 22.28 22.76 22.02

# 推荐使用loc和iloc的方式来获取数据的方式
data.loc[data.index[0:4], ['open', 'close', 'high', 'low']] # 获取的结果同上-ix方式
data.iloc[0:4, data.columns.get_indexer(['open', 'close', 'high', 'low'])] # 获取的结果同上

赋值操作

  • df[‘字段’] = 固定数值:改变DataFrame中某一列的全部值
    • df.字段 = 固定值:这个效果和上面的效果相同
1
2
3
4
5
6
# 对DataFrame中的close列重新赋值为1
data['close'] = 1 # 这种方法可行
# print(data)
# 或者
data.high = 1
print(data) # 这种方法也是可行的

排序操作

DataFrame排序操作分两种,一种是对内容进行排序,另一种是对索引进行排序

  • 对内容排序:
  • df.sort_values(by=’字段名称’, ascending=True/False):按照某个字段(股价),升序/降序对数据排列
    • 注意:这里的by是排序的依据,也可以是一个列表,如by=[‘open’, ‘high’]
  • 对索引排序:
  • df.sort_index()

Series的排序操作:排序之前需要搞到一个Series,所以Series = DataFrame[‘列名’]

  • 对内容排序:
  • Series.sort_values(ascending=True/False):因为只有一列,所以不需要排序依据by
  • 对索引排序:
  • Series.sort_index():不指定ascending的话默认为升序排序
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
# 排序操作分为DataFrame排序和Series排序操作
# 一种是对索引进行排序,另一种是对内容进行排序
import pandas as pd

# 对DataFrame进行排序
# 读取数据
data = pd.read_csv('./stock_day.csv')
# print(data)
# 删除一些列,让数据变得更简单一些,再去做后面的操作
data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)
# 对内容进行排序(按照股票开盘价格进行排序,升序且只显示前几个,默认为5)
data.sort_values(by="open", ascending=True).head()
# 按照多个键进行排序(按照开盘价格和最高价格进行升序排序,只取前三支股票)
data.sort_values(by=['open', 'high']).head(3)
# 上面的都是对内容进行排序,现在对索引进行排序,需要使用sort_index函数
data.sort_index()

# 对Series进行排序
# Series只有一列,不需要参数(先对内容进行排序)
data['price_change'].sort_values(ascending=True).head()
# Series对索引进行排序(接着就会按照索引升序排列)
data['p_change'].sort_index().head()
# 2015-03-02 2.62
# 2015-03-03 1.44
# 2015-03-04 1.57
# 2015-03-05 2.02
# 2015-03-06 8.51

DataFrame运算

算术运算&逻辑运算

一般用的比较少:

  • Series.add(常数):DataFrame中的一个Series(某一列)加上一个常数。
  • Series.sub(常数):同上,不想写了。
  • df[‘字段’] > Integer:逻辑运算,满足上述逻辑的会输出为True
  • df.isin([列表]):条件判断,DataFrame中存在列表中的数据就会标记为True
    • 同样可以使用嵌套进行筛选,比如:data[data[‘open’].isin([23.53, 23.85])]
  • df[df['字段'] > Integer & df['字段'] < Integer]:嵌套逻辑,可以筛选数据
    • 注意:这里的与是只有一个&,而不是通常的双与符号&&
  • df.query('字段 > Integer & 字段 < Integer '):通过query进行条件筛选,常用
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
import pandas as pd

# DataFrame算术运算
data['low'].add(1) # 将data的low列每一个数值都加1
data['low'].sub(1) # 将data的low列每一个数值都减1 但是不会改变data本身

# 逻辑运算
data['open'] > 23 # 将符合条件的数据设置为True
# 2018-02-27 True
# 2018-02-26 False
# 同时逻辑判断的结果可以所谓筛选数据的依据
data[data['open'] > 25]
# open high close low volume price_change p_change turnover
# 2018-01-24 25.49 26.28 25.29 25.20 134838.00 -0.20 -0.79 3.37
# 2018-01-23 25.15 25.53 25.50 24.93 104205.76 0.39 1.55 2.61
# 完成多个逻辑判断
data[(data['open'] > 23) & (data['open'] < 25)].head()
# open high close low volume price_change p_change turnover
# 2018-02-27 23.53 25.88 24.16 23.53 95578.03 0.63 2.68 2.39
# 2018-02-01 23.71 23.86 22.42 22.22 66414.64 -1.30 -5.48 1.66
# 2018-01-31 23.85 23.98 23.72 23.31 49155.02 -0.11 -0.46 1.23
# 2018-01-30 23.71 24.08 23.83 23.70 32420.43 0.05 0.21 70.81
# 2018-01-29 24.40 24.63 23.77 23.72 65469.81 -0.73 -2.98 1.64

# 逻辑运算函数(通过query使得上面的逻辑判断更简单)
data.query("open >23 & open < 25").head() # 执行结果同上
data['open'].isin([23.53, 23.85]) # 显示True和False
# 2018-02-27 True
# 2018-02-26 False
# 作为筛选数据的依据
data[data['open'].isin([23.53, 23.85])]

统计运算

Numpy当中已经详细介绍,在这里主要有如下的函数需要了解:

min(最小值), max(最大值), mean(平均值), median(中位数), var(方差), std(标准差),mode(众数)

  • DataFram.describe(): 综合分析函数,能够得出很多结果,如平均值,标准差,最大值,最小值

    image-20240824163141264

  • DataFram.max(0/1):求DataFram中的最大值,使用位置参数axis,0代表按照列统计,1按照行统计

  • DataFrame.var(0/1):方差

  • DataFrame.std(0/1):标准差

  • DataFrame.median(0/1):中位数

  • DataFream.idxmax(0/1):最大值的下标

  • DataFrame.idxmin(0/1):最小值的下标

cum系列函数,在 Pandas 中,cumsum() 是一个用于计算累积和(cumulative sum)的方法。它可以对 SeriesDataFrame 对象中的数据进行逐步累加,返回一个新的 SeriesDataFrame,其中每个元素是原始数据的累积和。

  • Series.cumsum():对Series中的数值进行累加

cum系列函数

函数 作用
cumsum 计算前1/2/3/…/n个数的和
cummax 计算前1/2/3/…/n个数的最大值
cummin 计算前1/2/3/…/n个数的最小值
cumprod 计算前1/2/3/…/n个数的积
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
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

data = pd.read_csv('./stock_day.csv')
# print(data)
# 数据太多没有用的列了,需要裁剪一些字段
data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)

# 紧接上面的逻辑运算,这里是统计运算
# 统计运算就是求count,中位数,平均数,最大值,最小值等一系列数学上的数值
data.describe()

# Numpy当中已经详细介绍,在这里主要练习min(最小值), max(最大值), mean(平均值), median(中位数), var(方差), std(标准差),mode(众数)结果
# 对于单个函数去进行统计的时候,可以指定是x轴,还是y轴(也就是按照数据的行或者列进行统计) axis=0代表按照列统计,axis=1代表按行统计
data.max(1) # 0 代表列求结果, 1 代表行求统计结果, 其实这里的参数为缺省参数,少了axis=
data.var(1) # 求方差,对每一列求
data.std(0) # 标准差
data.median(0) # 求中位数
data.idxmax(0) # 找出最大值的位置
data.idxmin(axis=0) # 找出最小值的位置

# cum系列函数(cumsum 计算1/2/3..n个数的和,就是累加,cummax 计算前n个数的最大值,cummin计算前n个数的最小值,cumprod计算前n个数的乘积)
data = data.sort_index()
# print(data)
stock_rise = data['p_change']
print(stock_rise)
stock_rise.cumsum() # 对data中的p_change这一列数据进行连续求和操作,通过matplotlib能更加直观的反应
stock_rise.cumsum().plot()
plt.show()

# 自定义运算(定义一个列中最大值-最小值的函数)apply(func, axis=0) func是自定义函数,axis是列
data[['open','close']].apply(lambda x: x.max() -x.min(), axis=0)

自定义运算apply

  • apply(func, axis=0)
    • func:自定义函数
    • axis=0:默认是列,axis=1为行进行运算
  • 定义一个对列,最大值-最小值的函数
1
2
3
4
5
6
7
8
# 使用apply接口,通过编写匿名函数Lambda作为apply接口的参数
# Lambda函数:定义Series的最大值减去最小值
# 整体:求open列和close列两列的最大值和最小值的差值
data[['open', 'close']].apply(lambda x: x.max() - x.min(), axis=0)

open 22.74
close 22.85
dtype: float64

Pandas绘图

由于之前搞了Matplotlib的画图,但是没有说Pandas本身也能绘图,类似的还有Seaborn绘图,Pyecharts绘图

DataFrame绘图

DataFrame.plot(kind=’line’)

  • kind : str,需要绘制图形的种类

  • ‘line’ : line plot (default)

  • ‘bar’ : vertical bar plot

  • ‘barh’ : horizontal bar plot

  • ‘hist’ : histogram

  • ‘pie’ : pie plot

  • ‘scatter’ : scatter plot

更多信息:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.html

Series绘图

更多信息:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.plot.html

文件读取与存储

Pandas的API支持众多的文件格式,如CSV、SQL、XLS、JSON、HDF5

csv、tsv文件

csv文件就是字段之间comma(逗号)分割,每条数据\n换行的格式文件;而tsv文件则是以\t作为字段之间分割,每条数据\n换行的格式文件。

  • pandas.read_csv(filepath_or_buffer, sep =’,’, usecols )
    • pandas可以简写为pd(前提是import pandas as pd)
  • DataFrame.to_csv(path_or_buf=None, sep=’, ’, columns=None, header=True, index=True, mode=’w’, encoding=None)
    • header :boolean or list of string, default True,是否写进列索引值
    • index:是否写进行索引
    • mode:’w’:重写, ‘a’ 追加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# pandas的API支持众多的文件格式,如CSV、SQL、XLS、JSON、HDF5。
# 读取csv文件(就是comma-逗号分割的一种文件)
data = pd.read_csv('./stock_day.csv', usecols=['open', 'close']) # 读取文件,且指定获取open和close的指标
# print(data)

# 将DataFrame写出为csv文件,可选参数path_or_buf 文件路径,index:是否写进行索引,mode:w代表重写,a代表追加,sep:分割符
# DataFrame.to_csv(path_or_buf=None, sep=', ’, columns=None, header=True, index=True, mode='w', encoding=None)
data[:10].to_csv('./test.csv', columns=['open']) # 保存open列的数据,选取10行数据保存,便于观察数据
pd.read_csv("./test.csv")
# Unnamed: 0 open
# 0 2018-02-27 23.53
# 1 2018-02-26 22.80
# 2 2018-02-23 22.88
# 3 2018-02-22 22.25

Json文件

JSON是我们常用的一种数据交换格式,前面在前后端的交互经常用到,也会在存储的时候选择这种格式。所以我们需要知道Pandas如何进行读取和存储JSON格式。

  • pandas.read_json(path_or_buf=None, orient=None, typ='frame', lines=False)

    • orient : string,Indication of expected JSON string format.

      • ‘split’ : dict like {index -> [index], columns -> [columns], data -> [values]}

        • split 将索引总结到索引,列名到列名,数据到数据。将三部分都分开了
      • ‘records’ : list like [{column -> value}, … , {column -> value}]

        • records 以columns:values的形式输出
      • ‘index’ : dict like {index -> {column -> value}}

        • index 以index:{columns:values}...的形式输出
      • ‘columns’ : dict like {column -> {index -> value}}

        ,默认该格式

        • colums 以columns:{index:values}的形式输出
      • ‘values’ : just the values array

        • values 直接输出值
    • lines : boolean, default False

      • 按照每行读取json对象
    • typ : default ‘frame’, 指定转换成的对象类型series或者dataframe

  • DataFrame.to_json(path_or_buf=None, orient=None, lines=False)

    • 将Pandas 对象存储为json格式
    • path_or_buf=None:文件地址
    • orient:存储的json形式,{‘split’,’records’,’index’,’columns’,’values’}
    • lines:一个对象存储为一行
1
2
3
4
5
6
7
8
9
# 将JSON格式准换成默认的Pandas DataFrame格式
# 读取json文件,orient是指定存储的json格式, lines指定为True是按照行去读取,以行为单位为一个样本
json_read = pd.read_json('./Sarcasm_Headlines_Dataset.json', orient='records', lines=True)
print(json_read)
# 写出csv文件:DataFrame.to_json(*path_or_buf=None*, *orient=None*, *lines=False*)
# orient: 存储json的格式,常见的有split,records,index,columns,values
# lines: 一个对象存储为一行
json_read.to_json('./test.json', orient='records') # 如果没有指定lines,则不是按照每个对象存储一行,则会导致出现一行很长的数据
json_read.to_json('./test.json', orient='records', lines=True)

HDF5文件

Python跟HDF5文件交互需要使用到一个Tables依赖包,如果没有的话pip install tables

HDF5文件的读取和存储需要指定一个键,值为要存储的DataFrame

  • pandas.read_hdf(path_or_buf,key =None,** kwargs)
    • path_or_buffer:文件路径
    • key:读取的键
    • return:Theselected object
  • DataFrame.to_hdf(path_or_buf, key, ***kwargs*)
1

缺失值处理

对于缺失值处理,需要掌握的东西:

  • 应用isnull判断是否有缺失数据NaN
    • pd.notnull(movie)
      • 标记DataFrame(movie)中不是Nan的为True,为Nan(空值)的为False

image-20240826223649949

image-20240826223708608

  • pd.isnull(movie)

    • 跟notnull相反
  • 应用fillna实现缺失值的填充

    • movie[‘Revenue (Millions)’].fillna(movie[‘Revenue (Millions)’].mean(), inplace=True)
      • 给税收这一列的所有nan值填充中位数
    • 替换所有的缺失值,有三步很重要的步骤
      • movie.columns
      • if np.all(pd.notnull(movie[i])) == False
      • movie[i].fillna(movie[i].mean(), inplace=True)
  • 应用dropna实现缺失值的删除

    • DataFrame.dropna():会直接删除Nan值所在的行

image-20240826223731473

  • 应用replace实现数据的替换
    • wis = wis.replace(to_replace=”?”, value=np.nan):先将?替换成nan类型的空值
    • wis = wis.dropna():删除nan类型的空值
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
import pandas as pd
import numpy as np

# 读取电影文件
movie = pd.read_csv('./IMDB-Movie-Data.csv')
# 判断缺失值是否存在
pd.notnull(movie) # 会将DataFrame中不为空值的标记为True,反之为空则为False
pd.isnull(movie) # 跟notnull相反

# numpy 结合pandas,np.all()判断numpy数组是否全部是True
np.all(pd.notnull(movie))

# 缺失值是nan标记的处理方式
# pandas删除缺失值,使用dropna的前提是,缺失值的类型必须是np.nan
movie.dropna() # 不修改原数据(在原来基础上新的DataFrame,可以用新的变量来接收他)

# pandas替换缺失值,分两种情况(填充用平均数,中位数)
movie['Revenue (Millions)'].fillna(movie['Revenue (Millions)'].mean(), inplace=True) # 给税收这一列的所有nan值填充中位数
# 替换所有缺失值,movie.columns获取所有的列,np.all(pd.notnull(df[列])):判断该列是否存在nan值,最后一步是替换fillna(mean, inplace=True)
for i in movie.columns:
if np.all(pd.notnull(movie[i])) == False:
print(i)
movie[i].fillna(movie[i].mean(), inplace=True)

# 缺失值不是nan的标记方式(不是nan就先替换为nan,是nan就替换)
# wis = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data")
# 如果上面读取报错 需要进行全局取消证书验证
# 全局取消证书验证
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
wis = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data")

# 先把不是nan的替换成nan,然后df.dropna()
wis = wis.replace(to_replace="?", value=np.nan)
# 删除nan
wis = wis.dropna()

数据离散化

数据离散化:连续属性的离散化就是在连续属性的值域上,将值域划分为若干个离散的区间,最后用不同的符号或整数 值代表落在每个子区间中的属性值。

举个例子:我们一般会将2000年出生到2009年出生的叫00后,1990-1999年生人为90后,就是聚类的问题。

  • qcut = pd.qcut(p_change, 10)
    • 对p_change这个DataFrame进行分组,分10个组,这个程序自己根据数据来分类的,每类数据都差不多
  • qcut.value_counts():
    • 计算分到每个组的数据个数
  • pd.cut(p_change, bins)
    • 根据前面那自定的bins区间进行分组(以bins作为分组依据),bins是一个列表,两两为一组
  • 独热编码
    • pandas.get_dummies(data, prefix=None)
      • 对DataFrame(data)获取独热编码,prefix前缀其实就是分组名称

image-20240828221821210

  • 自己总结:
    • 独热编码其实你有多少个类别的数据,就会有多少个列去唯一标识这些类别(慢慢体会吧,后面忘了再回顾
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
import pandas as pd

# 读取数据
data = pd.read_csv('./stock_day.csv')
p_change = data['p_change']
# 让Pandas对数据进行分组(一般会与value_counts)搭配使用,这种情况分组会
qcut = pd.qcut(p_change, 10)
# print(qcut)
# 分组情况:Categories (10, interval[float64, right]): [(-10.030999999999999, -4.836] < (-4.836, -2.444] < (-2.444, -1.352] < (-1.352, -0.462] ... (0.94, 1.738] < (1.738, 2.938] < (2.938, 5.27] < (5.27, 10.03]]
# 计算分到每个分组数据个数
qcut.value_counts()
# (-10.030999999999999, -4.836] 65
# (-0.462, 0.26] 65
# (0.26, 0.94] 65
# (5.27, 10.03] 65
# (-4.836, -2.444] 64
# (-2.444, -1.352] 64
# (-1.352, -0.462] 64
# (1.738, 2.938] 64
# (2.938, 5.27] 64
# (0.94, 1.738] 63
# 自定义区间分组 pd.cut(data, bins)
bins = [-100, -7, -5, -3, 0, 3, 5, 7, 100] # 自己指定分组区间
# 以bins作为分组的依据
p_counts = pd.cut(p_change, bins)
print(p_counts)
# 2018-02-27 (0, 3]
# 2018-02-26 (3, 5]
# 2018-02-23 (0, 3]
# 2018-02-22 (0, 3]
# 2018-02-14 (0, 3]
# ...
# 2015-03-06 (7, 100]
# 2015-03-05 (0, 3]
# 2015-03-04 (0, 3]
# 2015-03-03 (0, 3]
# 2015-03-02 (0, 3]

# 独热编码(简单理解就是对数据进行分类,然后把每个类别生成一个布尔列,这些列中只有一列可以为这个样本取值为1,这些编码方式被称为独热编码
# pandas.get_dummies(data, prefix=None)
dummies = pd.get_dummies(p_counts, prefix="rise") # 得出one-hot编码矩阵,prefix是分组名字

数据处理-合并

  • pd.concat([df1, df2], axis = 0/1)
    • 这种就是对两个DataFrame进行简单的拼接,axis中0为列索引,1为行索引
  • pd.merge(left, right, how=”inner”, on=None)
    • 可以按照两组数据的共同键值对合并或者左右各自,how可以进行内连接,左外,右外,on为外键

内连接:

image-20240902215941594

左连接:

image-20240902220025534

右连接:

image-20240902220058267

外连接:

image-20240902220121844

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
import pandas as pd
import numpy as np

# 数据合并的两个函数分别是pd.merge和pd.concat
# pd.concat([df1, df2], axis=0/1) # 按照行/列对两个DataFrame进行合并,0为列索引,1为行索引
# pd.merge(left, right, how="inner", on=None) # 可以按照两组数据的共同键值对合并或者左右各自,how可以进行内连接,左外,右外,on为外键

# 按照行索引进行
pd.concat([data, dummies], axis=1)

# pd.merge
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})

right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})

# 默认内连接
result = pd.merge(left, right, on=['key1', 'key2']) # 以key1和key2为连接主键
# 左连接
result = pd.merge(left, right, how='left', on=['key1','key2'])
# 右连接
result = pd.merge(left, right, how = 'right', on = ['key1', 'key2'])
# 外连接(其实是满外连接)
result = pd.merge(left, right, how='outer', on=['key1','key2'])

print(result)

交叉表与透视表

应用crosstab和pivot_table实现交叉表与透视表

  • 交叉表:

    交叉表用于计算一列数据对于另外一列数据的分组个数(用于统计分组频率的特殊透视表)

    • pd.crosstab(value1, value2)
  • 透视表:

    透视表是将原有的DataFrame的列分别作为行索引和列索引,然后对指定的列应用聚集函数

    • data.pivot_table()
    • DataFrame.pivot_table([], index=[])
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
import matplotlib.pyplot as plt
# 寻找星期几跟股票涨跌的关系
# 1, 先把对应的日期找到星期几(data.index实际上是['2018-02-27', '2018-02-26']列索引组成的列表,pd.to_datetime(df)是将df中的数据全部转为datetime64类型)
date = pd.to_datetime(data.index).weekday # df.weekday 是将dataframe中 的日期转为周几0-6

data['week'] = date # 将date这个DataFrame给data扩充一列,实际打印发现雀实
# print(data)

# 2, 把p_change按照大小去分个类,0为界限
data['posi_neg'] = np.where(data['p_change'] > 0, 1, 0)

# print(data)
# data这个DataFrame产生了两列数据
# week posi_neg
# 2018-02-27 1 1
# 2018-02-26 0 1
# 2018-02-23 4 1
# 2018-02-22 3 1
# 2018-02-14 2 1
# ... ... ...
# 2015-03-06 4 1
# 2015-03-05 3 1
# 2015-03-04 2 1
# 2015-03-03 1 1
# 2015-03-02 0 1

# 通过交叉表找寻两列数据的关系
count = pd.crosstab(data['week'], data['posi_neg'])
print(count)
# 但是我们看到count只是每个星期日子的好坏天数,并没有得到比例,可以对每个星期一的总天数求和,运用除法运算求出比例
# 算数运算,先求和
sum = count.sum(axis=1).astype(np.float32)
# 除以总数得出比例
pro = count.div(sum, axis=0)

# 使用plot画图,使用stacked的柱状图来查看效果
pro.plot(kind='bar', stacked=True)
plt.show()

# 使用pivot_table实现
# 通过透视表,使得刚才的过程更简单一些
data.pivot_table(['posi_neg'], index='week')

分组与聚合

应用groupby和聚合函数实现数据的分组与聚合

  • DataFrame.groupby(key, as_index=False)
    • key:分组的列数据,可以多个
  • 案例:不同颜色的不同笔的价格数据
1
2
3
4
5
6
7
8
col =pd.DataFrame({'color': ['white','red','green','red','green'], 'object': ['pen','pencil','pencil','ashtray','pen'],'price1':[5.56,4.20,1.30,0.56,2.75],'price2':[4.75,4.12,1.60,0.75,3.15]})

color object price1 price2
0 white pen 5.56 4.75
1 red pencil 4.20 4.12
2 green pencil 1.30 1.60
3 red ashtray 0.56 0.75
4 green pen 2.75 3.15
  • 进行分组,对颜色分组,price进行聚合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 分组,求平均值
col.groupby(['color'])['price1'].mean()
col['price1'].groupby(col['color']).mean()

color
green 2.025
red 2.380
white 5.560
Name: price1, dtype: float64

# 分组,数据的结构不变
col.groupby(['color'], as_index=False)['price1'].mean()

color price1
0 green 2.025
1 red 2.380
2 white 5.560

案例

1
2
3
4
5
6
7
8
9
10
11
12
# 导入星巴克数据
starbucks = pd.read_csv('./directory.csv')
# 按照国家分组,求出每个国家的星巴克零售店数量,先分组,再聚合
count = starbucks.groupby(['Country']).count()

count['Brand'].plot(kind='bar',figsize=(20,8))
plt.show()
print(count)

# 加入省市一起分租
multi = starbucks.groupby(['Country','State/Province']).count()
print(multi)

image-20240902222323642

End.