当前位置:   article > 正文

Pandas库介绍-CFTA注册金融科技分析师一级考点

np.array typeerror: '<' not supported between instances of 'nonetype' and 'f

Pandas是用于数据操纵和分析,建立在Numpy之上的。Pandas为Python带来了两种新的数据结构:Pandas Series和Pandas DataFrame,借助这两种数据结构,我们能够轻松直观地处理带标签数据和关系数据。

Pandas功能:

允许为行和列设定标签

可以针对时间序列数据计算滚动统计学指标

轻松处理NaN值

能够将不同的数据集合并在一起

与Numpy和Matplotlib集成

Series对象

Pandas series 是像数组一样的一维对象,可以存储很多类型的数据。Pandas series 和 Numpy array之间的主要区别之一是你可以为Pandas series 中的每个元素分配索引标签;另一个区别是Pandas series 可以同时存储不同类型的数据。

Series对象实际上都由两个数组组成,具有 index和 values两大属性。

index:保存标签信息,是从 NumPy数组继承的 Index对象;

values:保存值,是一维 NumPy数组对象。

1、同时指定index属性和value属性的具体值

2、仅制定values属性的值。如果只制定 values的取值,没有设定 index属性的取值,则会默认产生从0开始,步长为1,长度为 values

创建 Pandas Series

pd.Series(data, index)

groceries = pd.Series(data=[30, 6, 'yes', 'No'], index=['eggs', 'apples', 'milk', 'bread']) ser = pd.Series(data=[[0, 1, 2, 3], [1, 3, 5, 7], [2, 4, 6, 8]], index=(['a', 'b', 'c']))

注意:

1、 如果参数中只提供index值,未提供data,则会生成value全部为nan的空索引

2、 如果参数中只提供了data值,未提供index索引,则会生成index从0开始按顺序的数字索引

其它各种创建索引代码:

s1=pd.Series(np.random.randn(5))

s2=pd.Series([0,np.NaN,2,4,6,8,True, 10, 12])

s3=pd.Series({'a': 1,'b': 3,'c': 5,'d': 7})

s4=pd.Series([1,3,5,7,9], index=['a','b','c','d','e'])

查看 Pandas Series 属性

# Pandas Series 元素数量

print(groceries.size)

# Pandas Series 形状

print(groceries.shape)

# Pandas Series 维度

print(groceries.ndim)

# Pandas Series 索引列表

print(groceries.index)

# Pandas Series 元素列表

print(groceries.values)

查看是否存在某个索引标签:in

print('book' in groceries)

访问 Pandas Series 中元素

Pandas Series 提供了两个属性 .loc 和 .iloc

.loc 表明我们使用的是标签索引访问

.iloc 表明我们使用的是数字索引访问

# 标签索引

print(groceries['eggs'])

print(groceries[['eggs', 'milk']])

# 数字索引

print(groceries[1])

print(groceries[[1, 2]])

print(groceries[-1])

# 明确标签索引

print(groceries.loc['milk'])

print(groceries.loc[['eggs', 'apples']])

# 明确数字索引

print(groceries.iloc[0])

print(groceries.iloc[[0, 1]])

另外还有get函数,是专门根据索引获取值的:

Pandas Series.get()函数从给定键(DataFrame列,Panel slice等)的对象中获取项目。如果未找到,则返回默认值None;

返回值:

如果是单个key值查找,直接返回对应的值

如果是多个key同时查找,返回的是一个子Series对象

示例代码:

a = "I don't know how they've gotten away with all of that".split()

b = [91, 18, 27, 36, 15, 24, 33, 72, 31, 10, 56]

c = pd.Series(b, index = a)

print(c.get('all'))

print(c.get(['all','of']))

输出:

31

all 31

of 10

修改和删除元素

直接标签访问,值修改就可

groceries['eggs'] = 2

print(groceries)

删除:drop(参数 1:lable,标签;参数 2:inplace=True/False,是/否修改原 Series)

print(ser.drop(['b']))

print(ser.drop(['a', 'b'], inplace=True))

算术运算

Pandas Series执行元素级算术运算:加、减、乘、除

fruits = pd.Series(data=[10, 6, 3], index=['apples', 'oranges', 'bananas'])

# 所有数字进行运算

print(fruits + 2)

print(fruits - 2)

print(fruits * 2)

print(fruits / 2)

# 所有元素应用Numpy中的数学函数

print(np.exp(fruits))

print(np.sqrt(fruits))

print(np.power(fruits, 2))

# 部分元素进行运算

print(fruits[0] - 2)

print(fruits['apples'] + 2)

print(fruits.loc['oranges'] * 2)

print(np.power(fruits.iloc[0], 2))

时间序列介绍

时间序列类型也是一种 Series类型,与一般 Series对象不同的是,时间序列的 index属性取值为时间戳。

示例代码:

import pandas as pd

import numpy as np

#利用pandas生成日期数组作为索引,作为参数两边都会包含

date_idx=pd.date_range('2021/1/1','2021/12/31')

#新建时间序列索引

s=pd.Series(np.random.rand(len(date_idx)),index=date_idx)

#获取1到3月份的数据,注意切片时两头都会包含,和其它的切片不太一样

print(s['2021/1':'2021/3'])

说明:

1、时间序列的创建:方式与一般的 Series对象创建方式相同,我们只需要将 index设置为Timestamp对象。使用 to datetime()来将 Series的 index属性转换为 Datetimelndex:

2、截取时间段数据:一般的索引操作对时间序列依然有效,其特别之处在于对时间序列索引的操作优化。对于较长的序列,还可以只传入年份或年份加月份来选取切片:

3、滞后或超前操作

滞后操作指的是将t期的数据换成t一a期的数据,而超前操作自然是将t期的数据换成t+a期的数据。对于滞后或者超前操作,可以使用shift()方法。

排序

1、按索引进行排序

#定义一个Series

s = Series([1,2,3],index=["a","c","b"])

#对Series的索引进行排序,默认是升序

print(s.sort_index())

'''

a 1

b 3

c 2

'''

#对索引进行降序排序

print(s.sort_index(ascending=False))

'''

c 2

b 3

a 1

'''

2、按值进行排序

s = Series([np.nan,1,7,2,0],index=["a","c","e","b","d"])

#对Series的值进行排序,默认是按值的升序进行排序的

print(s.sort_values())

'''

d 0.0

c 1.0

b 2.0

e 7.0

a NaN

'''

#对Seires的值进行降序排序

print(s.sort_values(ascending=False))

'''

e 7.0

b 2.0

c 1.0

d 0.0

a NaN

'''

对值进行排序的时候,无论是升序还是降序,缺失值(NaN)都会排在最后面。

排名

排名和排序有点类似,排名会有一个排名值(从1开始,一直到数组中有效数据的数量),它与numpy.argsort的间接排序索引差不多,只不过它可以根据某种规则破坏平级关系。

s = Series([1,3,2,1,6],index=["a","c","d","b","e"])

#默认是根据值的大小进行平均排名

'''

1是最小的,所以第一个1排在第一,第二个1排在第二

因为取的是平均排名,所以1的排名为1.5

'''

print(s.rank())

'''

a 1.5

c 4.0

d 3.0

b 1.5

e 5.0

'''

#根据值在数组中出现的顺序进行排名

print(s.rank(method="first"))

'''

a 1.0

c 4.0

d 3.0

b 2.0

e 5.0

'''

method参数除了,first按值在原始数据中的出现顺序分配排名,还有min使用整个分组的最小排名,max是用整个分组的最大排名,average使用平均排名,也是默认的排名方式。还可以设置ascending参数,设置降序还是升序排序。

DataFrame对象

Pandas DataFrame 是具有带标签的行和列的二维数据结构,可以存储多种类型的数据,类似于电子表格。

Dataframe:一个表格型的数据结构每一列代表一个变量,而每一行则是一条记录。简单地说,DataFrame是共享同一个 index的Series的集合。

在创建 DataFrame对象之前,要先创建一个索引。索引是一个 DataFrame对象必须有的元素,起到标识的作用。

1、手动创建数据

2、从其他地方读取数据:read_table()函数、读取csv文件数据、读取MySQL数据。

创建 Pandas DataFrame

第一步:创建 Pandas Series 字典

第二步:将字典传递给 pd.DataFrame

items = {'Bob': pd.Series(data=[245, 25, 55], index=['bike', 'pants', 'watch']),

'Alice': pd.Series(data=[40, 110, 500, 45], index=['book', 'glasses', 'bike', 'pants'])}

shopping_carts = pd.DataFrame(items)

print(shopping_carts)

通过关键字 columns 和 index 选择要将哪些数据放入 DataFrame 中

shopping_cart = pd.DataFrame(items, index=['bike', 'pants'], columns=['Bob'])

print(shopping_cart)

访问、添加、删除 DataFrame

访问整列:dataframe[['column1', 'column2']]

# 读取列

print(shopping_carts[['Bob', 'Alice']])

访问整行:dataframe.loc[['row1', 'row2']]

# 读取行

print(shopping_carts.loc[['bike']])

访问某行某列:dataframe['column']['row'],先提供行标签,将出错。

# 读取某一列某一行

print(shopping_carts['Bob']['bike'])

take函数

Pandas dataframe.take()函数沿轴返回给定位置索引中的元素。这意味着我们没有根据对象的index属性中的实际值建立索引。我们正在根据元素在对象中的实际位置建立索引。

示例:

dataframe.take([2,4,0])

取第 3、5、1 三个值,如果按索引从0开始,则是2,4,0

添加整列(末尾添加列),空值用 None

# 添加列

shopping_carts['Mike'] = [10, 30, 10, 90, None]

添加整行(末尾添加行),把新添加行创建为 dataframe,通过 append() 添加

# 添加行

new_items = [{'Alice': 30, 'Bob': 20, 'Mark': 35, 'Mike': 50}]

new_store = pd.DataFrame(new_items, index=['store3'])

shopping_carts = shopping_carts.append(new_store)

只能删除整列:pop('lable')

# 删除整列

shopping_carts.pop('Jey')

删除行或者列:drop(['lable1', 'lable2'], axis=0/1) 0表示行,1表示列

# 删除行

shopping_carts = shopping_carts.drop(['store3', 'watch'], axis=0)

DataFrame更改行和列标签

rename()

# 更改列标签

shopping_carts = shopping_carts.rename(columns={'Bob': 'Jey'})

# 更改行标签

shopping_carts = shopping_carts.rename(index={'bike': 'hats'})

遍历

iterrows(): 将DataFrame迭代为(insex, Series)对。

iteritems(): 将DataFrame迭代为(列名, Series)对

itertuples(): 将DataFrame迭代为元祖。

示例代码:

import pandas as pd

inp=pd.DataFrame([{'c1':10,'c2':20},

{'c1':50,'c2':60},

{'c1':70,'c2':80}])

print(inp)

print('#'*50)

for idx,s in inp.iterrows():

print('idx',idx)

print(s)

print('#'*50)

for col_name,v in inp.iteritems():

print('col_name',col_name)

print(s)

print('#'*50)

for tu in inp.itertuples():

print(tu)

输出:

c1 c2

0 10 20

1 50 60

2 70 80

##################################################

idx 0

c1 10

c2 20

Name: 0, dtype: int64

idx 1

c1 50

c2 60

Name: 1, dtype: int64

idx 2

c1 70

c2 80

Name: 2, dtype: int64

##################################################

col_name c1

c1 70

c2 80

Name: 2, dtype: int64

col_name c2

c1 70

c2 80

Name: 2, dtype: int64

##################################################

Pandas(Index=0, c1=10, c2=20)

Pandas(Index=1, c1=50, c2=60)

Pandas(Index=2, c1=70, c2=80)

处理 NaN

统计 NaN 数量:isnull().sum().sum

# 数值转化为 True 或者 False

print(store_items.isnull())

# 每一列的 NaN 的数量

print(store_items.isnull().sum())

# NaN 总数

print(store_items.isnull().sum().sum())

统计非 NaN 数量:count(axis=0/1)

# 每一行非 NaN 的数量,通过列统计

print(store_items.count(axis=1))

# 每一列非 NaN 的数量,通过行统计

print(store_items.count(axis=0))

删除具有NaN值的行和列:dropna(axis=0/1, inplace=True/False) inplace默认False,原始DataFrame不会改变;inplace为True,在原始DataFrame删除行或者列

# 删除包含NaN值的任何行

store_items.dropna(axis=0)

# 删除包含NaN值的任何列

store_items.dropna(axis=1, inplace=True)

将 NaN 替换合适的值:fillna()

# 将所有 NaN 替换为 0

store_items.fillna(value=0)

# 前向填充:将 NaN 值替换为 DataFrame 中的上个值,axis决定列或行中的上个值

store_items.fillna(method='ffill', axis=1)

# 后向填充:将 NaN 值替换为 DataFrame 中的下个值,axis决定列或行中的下个值

store_items.fillna(method='backfill', axis=0)

加载数据

csv 格式文件,每一行都是用逗号隔开:read_csv()

# 读取 csv 文件,第一行作为列标签

data = pd.read_csv('data.csv')

print(data)

print(data.shape)

print(type(data))

读取前 N 行数据:head(N)

# 读取头 3 行数据

print(data.head(3))

读取最后 N 行数据:tail(N)

# 读取后 5 行数据

print(data.tail(5))

检查是否有任何列包含 NaN 值:isnull().any()  类型 bool

# 检查任何列是否有 NaN 值,返回值:bool

print(data.isnull().any())

数据集的统计信息:describe()

# 获取 DataFrame 每列的统计信息:count,mean,std,min,25%,50%,75%,max

# 25%:四分之一位数;50%:中位数;75%:四分之三位数

print(data.describe())

# 通过统计学函数查看某个统计信息

print(data.max())

print(data.median())

数据相关性:不同列的数据是否有关联,1 表明关联性很高,0 表明数据不相关。corr()

# 数据相关性

print(data.corr())

数据分组:groupby(['lable1', 'lable2'])

# 按年份分组,统计总薪资

data.groupby(['Year'])['Salary'].sum()

# 按年份分组,统计平均薪资

data.groupby(['Year'])['Salary'].mean()

# 按年份,部门分组,统计总薪资

data.groupby(['Year', 'Department'])['Salary'].sum()

索引与切片

将DataFrame中的某一列替换为索引:

df.set_index("period",inplace=True)

从现有的DataFrame中切出一个索引:

s1=pd.Series(df.loc['202012']['item'])

其中:’202012’代表索引过滤为202012的,item为具体需要新建索引的列(因为本质上Series对象即为DataFrame的某一列)

标签索引与切片:df.loc[row_ indexer, column_indexer]

位置索引与切片:df.iloc[row_ indexer, column_indexer]

shift函数

dataframe[“col”].shift(-1): 某列整体前(上)移 1 位,空缺出来的用Nan补齐

dataframe[“col”].shift(1): 某列整体后(下)移 1 位,空缺出来的用Nan补齐

行列转置

假设df是一个现成的DataFrame,一般使用df.T函数进行行列转置

df_T = pd.DataFrame(df.values.T,columns=index_row,index=index_colums)

print(df_T)

排序

1、 按索引进行排序

a = np.arange(9).reshape(3,3)

data = DataFrame(a,index=["0","2","1"],columns=["c","a","b"])

#按行的索引升序进行排序,默认按行,升序

print(data.sort_index())

'''

c a b

0 0 1 2

1 6 7 8

2 3 4 5

'''

#按行的索引按降序进行排序

print(data.sort_index(ascending=False))

'''

c a b

2 3 4 5

1 6 7 8

0 0 1 2

'''

#按列升序的索引进行排序

print(data.sort_index(axis=1))

'''

a b c

0 1 2 0

2 4 5 3

1 7 8 6

'''

#按列降序的索引进行排序

print(data.sort_index(ascending=False))

'''

c a b

2 3 4 5

1 6 7 8

0 0 1 2

'''

2、按值进行排序

a = [[9,3,1],[1,2,8],[1,0,5]]

data = DataFrame(a, index=["0", "2", "1"], columns=["c", "a", "b"])

#按指定列的值大小顺序进行排序

print(data.sort_values(by="c"))

'''

c a b

2 1 2 8

1 1 0 5

0 9 3 1

'''

print(data.sort_values(by=["c","a"]))

'''

c a b

1 1 0 5

2 1 2 8

0 9 3 1

'''

#按指定行值进行排序

print(data.sort_values(by="0",axis=1))

'''

b a c

0 1 3 9

2 8 2 1

1 5 0 1

'''

注意:对DataFrame的值进行排序的时候,我们必须要使用by指定某一行(列)或者某几行(列),如果不使用by参数进行指定的时候,就会报TypeError: sort_values() missing 1 required positional argument: 'by'。使用by参数进行某几列(行)排序的时候,以列表中的第一个为准,可能后面的不会生效,因为有的时候无法做到既对第一行(列)进行升序排序又对第二行(列)进行排序。在指定行值进行排序的时候,必须设置axis=1,不然会报错,因为默认指定的是列索引,找不到这个索引所以报错,axis=1的意思是指定行索引。

排名

排名和排序有点类似,排名会有一个排名值(从1开始,一直到数组中有效数据的数量),它与numpy.argsort的间接排序索引差不多,只不过它可以根据某种规则破坏平级关系。

a = [[9, 3, 1], [1, 2, 8], [1, 0, 5]]

data = DataFrame(a, index=["0", "2", "1"], columns=["c", "a", "b"])

print(data)

'''

c a b

0 9 3 1

2 1 2 8

1 1 0 5

'''

#默认按列进行排名

print(data.rank())

'''

c a b

0 3.0 3.0 1.0

2 1.5 2.0 3.0

1 1.5 1.0 2.0

'''

#按行进行排名

print(data.rank(axis=1))

'''

c a b

0 3.0 2.0 1.0

2 1.0 2.0 3.0

1 2.0 1.0 3.0

'''

method参数和ascending参数的设置与Series一样。

数据规整化

数据规整的一般分类:

清理

转换

合并

重塑

Pandas数据规整-清理

对指定数据(如缺失数据、重复数据)进行处理(检查、替换、删除)

缺失值的表示:np.nan

检查缺失值:isnull(),notnull(),info()

删除缺失值:dropna()

填充缺失值:fillna()

替换值(填充缺失值是替换值的一种情况):replace()

------------------------------

移除重复数据

检测和过滤异常值

实例代码:

a = np.array([2,4,5,7,8,9])

a + 10

# result array([12, 14, 15, 17, 18, 19])

b = np.array([None,1,4,6,8,4,23,67])

b + 10

# result TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

结论:缺失值导致报错

Numpy的缺失值数据类型:np.nan

缺失值运算不会报错,和缺失值进行运算,结果还是缺失值

a = np.array([np.nan,1,4,5])

a + 10

a.sum() # 任何数组和缺失值计算,结果还是缺失值

np.nansum(a)

np.nanmean(a) # result 3.3333333333333335 ,缺失值不参与运算

nan专有运算方法,会跳过缺失值,直接计算正常值,缺失值不参与运算

np.nansum(c)

np.nanmean(c)

使用Pandas缺失值计算

Pandas中,不论缺失值是 None 还是 np.nan ,都会转化为 NaN 的形式

NaN:非数字,not a number,Pandas中它表示缺失或NA值,便于被检测出来

本质上就是np.nan

Pandas的对象可以跳过缺失值直接进行运算

b = pd.Series([1,2,np.nan,4,None,6])

b+10

#reslut

#0 11.0

#1 12.0

#2 NaN

#3 14.0

#4 NaN

#5 16.0

b.mean() # 4.0

# 缺失值赋值

b[0] = np.nan

c = pd.DataFrame([[1,np.nan,3], [4,5,6], [np.nan,8,9]])

c.sum(axis=1)

是否含有缺失值

检查单个空值

单个空值,底层类型为 np.nan,不能直接比较是否相同

# 单个空值,底层类型为 np.nan,不能直接比较是否相同

np.nan == np.nan

# 单个np.nan空值,可以用 np.isnan() 方法判断是否是空值

np.isnan(np.nan)

# 整体判断,表格中各列缺失值情况

c.info() # dataframe

np.isnan(c)

np.isnan(b) # series

isnull()和notnull()

isnull():缺失值返回True,正常值返回False

notnull():正常值返回True,缺失值返回False

b.isnull()

# 过滤非空

b[-b.isnull()]

b.notnull() # 同上, 选中所有非缺失值

b[b.notnull()]

# dataframe 使用isnull notnull的时候无法具体分清

c.isnull()

c[c.isnull()]

c.notnull()

c[c.notnull()]

结论:DataFrame不能通过布尔查询方式过滤缺失值,必须使用Pandas的特定方法过滤

查到缺失值后,Series可以直接过滤,DataFrame需要进一步处理(填充或删除)

去除缺失值

# series

b.dropna()

# 等同于上:查询所有非缺失值

b[b.notnull()]

# dataframe

c.dropna() # 默认按行

c.dropna(axis=1) # 按列删除

# 增加一列全部为缺失值的数据

c[3] = np.nan

# 行或列,有1个缺失值即删除

c.dropna(axis=1) # 简写,默认

c.dropna(axis=1, how='any')

# 行或列必须全部都是缺失值才删

c.dropna(axis=1, how='all')

c.dropna(thresh=3) # 行非缺失值数量大于等于3个,保留

填充缺失值

缺失值问题除了删除所在行列以外,还可以通过填充值解决

fillna()函数参数

c.loc[0, 1] = np.nan

c.loc[1:3,0] = np.nan

c.at[3,3] =100 #导致出现了第四行数据

c[3] = np.nan

c.fillna(method='ffill')

c.fillna(method='ffill',limit=3) # 设置填充改变的数量,limit=3每一列可以有三行数据改变

替换值

利用fillna方法填充缺失数据是值替换的一种特殊情况, replace方法用作替换值更简单、更灵活

data = pd.Series([1,-999,2,-999,-1000,3])

# 替换单值

data.replace(-999, np.nan)

# 替换多值, 多个值替换为1个

data.replace([-999, -1000], np.nan)

# 多个值替换为不同数值

data.replace([-999, -1000], [0, 1])

data.replace({-999: 0, -1000: 1})

#dataframe也是如此,直接替换值

c.replace(1,-1)

映射数据替换

map除了自定义函数运算,还是一种映射转换元素以及其他数据清理工作的便捷方式

a = pd.DataFrame([['鬃刷','皮带','煎蛋','观赏'],[10,20,30,40]]).T # 转置

y = {'鬃刷': '猪', '皮带': '牛', '观赏': '鱼', '衣服': '棉花'}

a[0].map(y)

移除重复数据

移除DataFrame的重复行

data = pd.DataFrame({'k1':['one'] * 3 + ['two'] * 4,'k2':[1,1,2,3,3,4,4]})

# 布尔型Series,各列重复值交集

data.duplicated()

# 重复行的移除

data.drop_duplicates()

data[-data.duplicated()]

# 移除自定义列重复行

data.drop_duplicates('k1') 或data.drop_duplicates(['k1']) (多个列可以在list中增加)

#keep : {‘first’, ‘last’, False},

# first默认留下第一次出现的值

data.drop_duplicates(['k1', 'k2'])

data.drop_duplicates(['k1', 'k2'], keep='first')

# last,留下最后一次出现的值

data.drop_duplicates(['k1', 'k2'], keep='last')

# 保留非全列的行数据时,结果行会不同

data.drop_duplicates(['k1'])

data.drop_duplicates(['k1'], keep='last')

# False,删掉所有重复值

data.drop_duplicates(['k1', 'k2'], keep=False)

移除重复索引值

obj = pd.Series(range(5), index = ['a','a','b','b','c'])

obj['a']

obj[obj.duplicated()]

obj[~(obj.index.duplicated())] # 查询不重复索引对应的值

检测和过滤异常值(了解)

过滤或变换异常值(outlier)在很大程度上就是运用数组运算

例子:一个含有正态分布数据的DataFrame

data = pd.DataFrame(np.random.randn(1000, 4))

data[0:3]

data[2][(data[2]>3) | (data[2]<-3)]

data[2][np.abs(data[2])>3]

# 找出全部绝对值大于3的值所在的行

data[np.abs(data)>3].dropna(how='all')

data[np.abs(data) > 3]

data[np.abs(data) > 3].any(axis=1) #前面的条件只要有一个生效就是true,一个没有就是False

data[data[np.abs(data) > 3].any(axis=1)]

a = np.array([3, -4, 7, -100])

np.sign(a) # 判断数据正负,正数1,负数-1,生成一个对应数据的1,-1数据

# 将数据范围限制在3到-3之间(大于3的改为3,小于-3的改为-3)

np.sign(data)

data[np.abs(data) > 3] = np.sign(data) * 3

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/540957
推荐阅读
相关标签
  

闽ICP备14008679号