【Pandas学习笔记Task02】:pandas基础
一、pandas数据结构
pandas的基本数据结构,包括一维的Series和二维的DataFrame。Series可以理解为一个字典,DataFrame就是Series的一个扩展。
1.Series
s = pd.Series(data = [100, 'a'], index= ['id1', 20], name='my')
# id1 100
# 20 a
# Name: my, dtype: object
# index是以Index对象存的,可以转为列表
print(list(s.index)) # ['id1', 20]
# 利用[index_item]取出单个索引对应的值.(这里是Series,DataFrame利用[index_item]取的是列索引)
print(s['id1']) # 100
2.DataFrame
data = [[1, 'a', 1.2], [2, 'b', 2.2], [3, 'c', 3.2]]
# index代表行索引,column代表列索引
df = pd.DataFrame(data, index= ['row_%d'%i for i in range(3)], columns=['col_%d'%i for i in range(3)])
# col_0 col_1 col_2
# row_0 1 a 1.2
# row_1 2 b 2.2
# row_2 3 c 3.2
# 这里data存的字典,每个value代表是一列。如果指定了column,需指定index否则会提示DataFrame为空,index为空。而data是字典形式已经给出了列名,后面若再给columns则所有值都是NaN。
df = pd.DataFrame(data = {'col_0': [1, 2, 3], 'col_1': ['a', 'b', 'c'], 'col_2': [1.2, 2.2, 3.2]},
columns = ['row_%d'%i for i in range(3)])
# Empty DataFrame
# Columns: [row_0, row_1, row_2]
# Index: []
3、对DataFrame行列的访问
print(df.values) # 得到的是df里面的值组成列表
print(df['School']) # 以字典形式访问就是访问某列,其实得到的是Series.
print(df[['School', 'Grade']]) # 访问某些特定列,把要访问的列以列表传给df[],其实还是DataFrame.
print(df.School) # 也可以直接将列名当做属性访问
print(df[6:7]) # 可以使用切片的形式访问第七行,若直接用7访问第7行会报错
print(df.iloc[1]) # iloc代表的是访问位置,使用iloc访问某一行(也可以访问某行某列)
print(df[df.columns[:7]]) # 访问df的前七列.先得到前七列索引值,然后再用df去访问
二、常用函数
1.汇总函数
head、tail函数分别表示可以取前后者后n行的结果,默认为5.
df = pd.read_csv('data/learn_pandas.csv')
df = df[df.columns[:7]]
# 取前两行
print(df.head(2))
# School Grade ... Weight Transfer
# 0 Shanghai Jiao Tong University Freshman ... 46.0 N
# 1 Peking University Freshman ... 70.0 N
#
# [2 rows x 7 columns]
# 取最后两行
print(df.tail(2))
# School Grade ... Weight Transfer
# 198 Shanghai Jiao Tong University Senior ... 71.0 N
# 199 Tsinghua University Sophomore ... 51.0 N
#
# [2 rows x 7 columns]
#info得到表的基本信息,describe得到表中数值列对应的一些统计量
df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 200 entries, 0 to 199
# Data columns (total 7 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 School 200 non-null object
# 1 Grade 200 non-null object
# 2 Name 200 non-null object
# 3 Gender 200 non-null object
# 4 Height 183 non-null float64
# 5 Weight 189 non-null float64
# 6 Transfer 188 non-null object
# dtypes: float64(2), object(5)
# memory usage: 11.1+ KB
df.describe()
# Height Weight
# count 183.000000 189.000000
# mean 163.218033 55.015873
# std 8.608879 12.824294
# min 145.400000 34.000000
# 25% 157.150000 46.000000
# 50% 161.900000 51.000000
# 75% 167.500000 65.000000
# max 193.900000 89.000000
#
2.特征统计函数
在 Series 和 DataFrame 上定义了许多统计函数,最常见的是 sum, mean, median, var, std, max, min 。这些统计函数与之前学习numpy时的大同小异。
df_demo = df[['Height', 'Weight']]
print(df_demo.mean()) # 每一列的平均值
print(df_demo.quantile(0.75)) # 列的分位数
print(df_demo.count()) # 非缺失值个数
# 函数返回标量,又称为聚合函数。有axis参数代表维度。0代表按列聚合(默认),1代表按行聚合。联想到numpy中的一些统计函数如sum()
3.唯一值函数
# 唯一值函数:unique得到唯一值组成的列表,nunique得到唯一值的个数.
print(df['School'].unique()) # 只支持1列,否则报错,因为DataFrame没有该属性
print(df['School'].nunique()) # 4 DataFrame有,但其实也是按多个Series来计算的,如下
print(df[['School','Grade']].nunique())
# School 4
# Grade 4
# dtype: int64
print(df['School'].value_counts()) # 得到唯一值以及其有多少个值
# 多个列组合的唯一值要用drop_duplicates(列名),keep的值last代表保留最后出现该组合的行
# False和first、last有区别
# keep等于False,表示若该组合有值重复出现,那这组值所在的所有行都删掉(EX1第2问体会到)
print(df.drop_duplicates(['School', 'Grade'], keep='last'))
4.替换函数
一般来说替换针对Series,也就是某一列。
替换包括三类:映射替换、逻辑替换、数值替换。
其中映射替换可以理解为将x→y;
逻辑替换可以理解为满足/(不满足)条件则执行某种替换;
# 映射替换,可以通过字典,也可以通过列表实现对应位置的替换
# 这里将Male替换为0;Female替换为1
print(df['Gender'].replace({'Male': 0, 'Female': 1}).head())
print(df['Gender'].replace(['Female', 'Male'], [0, 1]).head())
# 逻辑替换 where(和numpy的where像), mask
s = pd.Series([-1, 1.123, 1000, 21])
print(s.where(s < 0, 100)) # 满足括号里面条件的不换
print(s.mask(s > 10, 20)) # 满足括号条件的换
bo = [True, False, False, False]
print(s.where(bo, 20)) # 传入一个布尔值的列表来表示谁换,换成什么。
# 数值替换 round, abs, clip方法
s = pd.Series([-1, 1.123, 1000, 221])
print(s.round(2)) # 取整,参数代表n位
print(s.abs()) # 绝对值
print(s.clip(1,100)) # 截断,前两个数字表示上下截断边界
5.排序函数
排序分为按照值排序(sort_values) 和按照索引排序(sort_index)。
# 值排序
print(df.sort_values('Height').head()) # 按height的值排序
# 对多个列排序,会先排sort_values指定排序列表前面的,若相同,则排后面的,默认升序,可以在ascending中指定升序(True)降序(False)
print(df.sort_values(['Weight', 'Height'], ascending=[True, False]).head())
# 索引排序,按照要排序的索引的值对表进行排序
df = df.set_index(['Weight', 'Height']) # 做个测试,首先将weight和Height设为索引
print(df.sort_index(level=['Weight', 'Height'], ascending=[False, False]).head())
练习
Ex1:口袋妖怪数据集
.现有一份口袋妖怪的数据集,下面进行一些背景说明:
- # 代表全国图鉴编号,不同行存在相同数字则表示为该妖怪的不同状态
- 妖怪具有单属性和双属性两种,对于单属性的妖怪, Type 2 为缺失值
- Total, HP, Attack, Defense, Sp. Atk, Sp. Def, Speed 分别代表种族值、体力、物攻、防御、特攻、特防、速度,其中种族值为后6项之和
对 HP, Attack, Defense, Sp. Atk, Sp. Def, Speed 进行加总,验证是否为 Total 值。
对于 # 重复的妖怪只保留第一条记录,解决以下问题:
求第一属性的种类数量和前三多数量对应的种类
求第一属性和第二属性的组合种类
求尚未出现过的属性组合按照下述要求,构造 Series :
取出物攻,超过120的替换为 high ,不足50的替换为 low ,否则设为 mid
取出第一属性,分别用 replace 和 apply 替换所有字母为大写 求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到 df 并从大到小排序
df = pd.read_csv('data/pokemon.csv')
print(df.head(3))
1、对 HP, Attack, Defense, Sp. Atk, Sp. Def, Speed 进行加总,验证是否为 Total 值。
df_demo1 = df[df.columns[5:]] # 先得到这些属性组成的df
# print(df_demo1.sum(1).head()) # 求行的和,这不就是逐行聚合嘛
if(list(df_demo1.sum(1)) == list(df['Total'])):
print('True')
else:
print('False')
# 参考答案
(df[['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']].sum(1)!=df['Total']).mean()
通过列索引获取属性的值然后逐行求和,将值与total进行比较即可。但是这里如果不利用list转为列表的话不能直接用==比较,会报错ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().。因为原本都是属于Series,我的理解是值类型不明确,所以转为list后可以进行比较。后来大佬解释了,为什么用if不行:
if里面只能传一个布尔值,序列之间判断之后返回的是bool的Series,里面有不同的bool,if不知道根据哪一个判断,如果一定要用的话就改成if(df_demo1.sum(1)!=df.Total).all()
(这些细节让我备受打击,也让我感受到要学习的还有太多太多…too much…)
2、
df_demo2 = df.drop_duplicates(['#'], keep='first')
print(df_demo2)
# a 种类数量
tp1_type_num = df_demo2['Type 1'].nunique()
print(tp1_type_num) # 18
# a 前三多数量对应的种类
top3_tp1_num = df_demo2['Type 1'].value_counts()[:3]
print(top3_tp1_num)
# # Water 105
# # Normal 93
# # Grass 66
# Name: Type 1, dtype: int64
# b 求第一属性和第二属性的组合种类
# 先只得到这两列组成的表,不指出列的话,默认按全部列来算
p_type = df_demo2[['Type 1', 'Type 2']].drop_duplicates(keep='first')
# # [143 rows x 2 columns]
# c 求尚未出现过的属性组合(解答错误)
tp2_type_num = df_demo2['Type 2'].nunique() # 也是18,去掉了nan
# 从Type1和Type2的(不重复)种类中分别选值然后组合,判断该组合是否在出现过的组合种类集合里
tp1_type = df_demo2['Type 1'].unique()
tp2_type = df_demo2['Type 2'].unique() # 这里有nan值,不知如何处理了 (19),但是nunique是18
unappear_type = [[tp1, tp2] for tp1 in tp1_type
for tp2 in tp2_type
if (tp1, tp2) not in tp_type.values.tolist()] # 把nan算作一个值
第2问中,a、b问题可以直接通过相关函数进行提取。但是在c问题上我的上述解法存在问题。
我的思路是:从Type1和Type2的(不重复)种类中分别选值然后组合,判断该组合是否在出现过的组合种类集合里,若不在那就是未出现过的属性组合。
想法过于美好(未考虑复杂度问题),在实现过程中的最后一步出岔子了。已经实现找到Type1与Type2的种类,通过b问题也得到了表中出现的组合种类,然后打算通过两个遍历拿到(Type1,Type2)值的数对,然后利用not in来判断是否存在已出现的组合种类中。问题就出现在这里,not in无法实现判断是否在列表里面,可能因为这是一个二维的吧。
于是做了一个测试:
l1 = [[1, 2], [2, 3]]
print([[i, j] for i in range(3) for j in range(3) if (i, j) in l1])
# []
看来有些基础还未理解透彻。这里不能直接用not in来判断了。
3、
df_demo3 = df['Attack']
# 不能将元素取完放回再取,共三次比,而是将该元素连续比三次得到结果('>' not supported between instances of 'str' and 'int' )
# 不能使用and,逻辑运算符,只能用&执行位运算,因为df_demo3是一个Series形式而不是数值去参与比较
df_demo3 = df_demo3.mask(df_demo3 > 120, 'high').mask(df_demo3 < 50, 'low').mask((df_demo3 >= 50) & (df_demo3 <= 120), 'mid')
print(df_demo3)
第三题一开始我用三条语句进行分别比较,但是发现自己太天真。因为替换前是整型替换后就是字符了,执行比较时就报错(’>’ not supported between instances of ‘str’ and ‘int’)看了参考答案才发现原来可以直接连用三个逻辑替换,于是不经意中又采用and进行连接两个条件的,但是会报错。因为不能使用and逻辑运算符,只能用&执行位运算,因为df_demo3是一个Series形式而不是数值去参与比较,而且两个括号也要括起来。(我太难了 too sad.)
b 取出第一属性,分别用 replace 和 apply 替换所有字母为大写
# replace
df_demo4 = df['Type 1'].replace(tp1_type, [st.upper() for st in tp1_type])
print(df_demo4)
# apply
df_demo5 = df['Type 1'].apply(lambda x: x.upper())
print(df_demo5)
这里replace方法就是按照其参数模板,往里面传要替换的值。而且字符串有内置的转字母为大写的函数;在用apply方法之前觉得挺难,写起来发现对于这道题…很快啊,传一个变大写的函数就搞定,而且这个还是内置的方法,再看看答案,interesting.
求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到 df 并从大到小排序
df_demo6 = df[df.columns[5:]]
df['deviation'] = df_demo6.apply(lambda x: np.max((x - x.median()).abs()), 1)
print(df.sort_values('deviation', ascending=False).head())
这里apply的运用过程稍微复杂一些,直接上答案了。看了才知道,哦原来是,按行取元素然后减去中位数取绝对值并找到最大的值,这些我都能想到,但就是实现起来…
这道题本来还疑惑如何给DataFrame添加列,原来直接给一个索引值就行。(想想确实没什么啊,没把字典联系起来,哎,看来思考这道题时思维太局限了,发散思维有待加强)
总结
通过学习,感觉虽然很多方法看着很简单,但是因为自己还不太熟练的缘故,具体应用起来就有些困难。重要的在于思维,而且要将解题思维正确表达出来,还有很长的路要走。这一部分有很多的东西也还没有完全厘清,还要继续进一步学习。