Python数据分析易错知识点归纳(三):Pandas
三、pandas
不带括号的基本属性
df.index # 结果是一个Index对象, 可以使用等号重新赋值,如: df.index = ['a', 'b', 'c']
df.columns # 结果是一个Index对象,可以使用等号重新赋值,如: df.columns = ['A', 'B', 'C']
# 在对Index对象操作时,可以直接当list使用,不用特意通过tolist()转成list
data = data[[col for col in data.columns if col not in del_cols]]
df.values # 注意不能使用等号重新赋值!!!
df.shape 返回元组
df.size 总个数
df.dtypes
# 返回布尔值,判断对象是否为空
df.empty
设置不隐藏
np.set_printoptions(threshold=1e6)
pd.set_option('display.max_columns', 1000)
pd.set_option('display.width', 1000)
pd.set_option('display.max_colwidth', 1000)
# 列名与数据对齐显示
pd.set_option('display.unicode.ambiguous_as_wide', True)
pd.set_option('display.unicode.east_asian_width', True)
df.info()
df.describe()
print(df.describe())
'''
a b
count 9.000000 5.000000
mean 2.777778 2.200000
std 1.201850 0.273861
min 1.000000 2.000000
25% 2.000000 2.000000
50% 3.000000 2.000000
75% 3.000000 2.500000
max 5.000000 2.500000
'''
print(df.describe(include=['object'])) #显示freq最高的对象
# 或print(df.describe(include=object))
print(df.describe(include='all'))
df.astype
# np.int8, np.int16, np.int32, np.int64 四种数据类型可以使用字符串 'i1', 'i2','i4','i8' 代替
dt1 = np.dtype(np.int8)
dt2 = np.dtype('i8')
# np.float32, np.float64
#np.float64占用64个bits,每个字节长度为8,所以64/8,占用8个字节
f = np.array([1, 2, 3, 4, 5], dtype=np.float64)
# 在pandas中若不考虑存储空间和方式的问题,可以简单使用int,float,str即可
for col_name in data.columns:
if col_name in float_col_list:
data[col_name] = data[col_name].astype(float)
elif col_name in str_col_list:
data[col_name] = data[col_name].astype(str)
else:
data[col_name] = data[col_name].astype(int)
df.idxmin()/df.idxmax
# 返回某列或某几列最小/最大值所在索引
df[col_name].idxmin()
df.idxmin()
df.sort_index()
asc_sorted_df = unsorted_df.sort_index()
desc_sorted_df = unsorted_df.sort_index(ascending=False)
col_sorted_df = unsorted_df.sort_index(axis=1)
df.sort_values()
注意:若前面没有=,一定要带inplace=True, 否则不起作用
注意:若df本身就是通过切片来的,使用inplace=True可能会有报警,因此最好使用等号重新赋值更新
col1_sorted_df = unsorted_df.sort_values(by='col1')
# 或直接改变原数据
unsorted_df.sort_values(by='col1', inplace=True)
col1_col2_sorted_df = unsorted_df.sort_values(by=['col1', 'col2'])
# 实际过程中:若想取到排序之后的第一行行号
df_sort.index[0]
# 或者使用df[col_name].idxmin()或df[col_name].idxmax()
# 若想排序之后,去掉原索引名,将行索引号赋值给行索引名,可以使用reset_index(drop=True)
data_gr = data_gr.sort_values(by='count', ascending=False).reset_index(drop=True)
df[col_name].value_counts()
#注意:1,只能用于Series;2.返回一个Series,按照出现频率按多到少排序,index为原value
print(data['a'].value_counts())
# 注意以下方法只适合目标value为0,1类型的数据
# 巧用value_counts()和groupby计算不同...类型...的比率
cp_count = data['cp'].value_counts() # cp为胸痛类型 # 输出为Series
gr_cp_sum = data.groupby('cp')['target'].sum() # target为1表示患病,0表示不患病 # 输出为Series
print('不同胸痛类型的就诊者所患病的比率\n', round(gr_cp_sum / cp_count, 2))
df[col_name].unique() / df[col_name].nunique()
# unique()与np.unique()效果一样,返回去重之后的结果,若原数据中包含NaN, 去重之后结果中也会有
# nunique(): 返回去重之后的元素个数,可以使用参数dropna=False(默认是True)决定是否包含NaN
a.nunique() # 默认dropna=True
a.nunique(dropna=False) # 不过滤NaN
# 应用:过滤值全一样的列
data = data.iloc[:, [data[col].nunique() != 1 for col in data.columns]]
df.reindex()与df.reindex_like()与df.rename()与df.reset_index()(重建索引)
# 注意不要被reindex的名字所迷惑,其功能类似于切片,如:取0,2,100行,'A', 'B', 'Z'列;不同之处是,可以是原df中不存在的行或列,生成新的df中,这里行或列为NaN
df_reindexed = df.reindex(index=[0,2,100], columns=['A', 'B', 'Z'])
# 若想重建索引,直接给df.index赋值即可
df.index = range(len(df))
df1 = pd.DataFrame(np.random.randn(6,3),columns=['col1','col2','col3'])
df2 = pd.DataFrame(np.random.randn(2,3),columns=['col1','col2','col3'])
df1 = df1.reindex_like(df2) # 必须确保df1和df2列明相同 # df1的行数变成与df2一样
# rename通过字典结构重建,注意:需要重新赋值或使用inplace=True
df1 = df1.rename(columns={'col1':'c1','col2':'c2'},index ={0 :'apple',1 :'banana',2 :'durian'})
# 或
df1.rename(columns={'col1':'c1','col2':'c2'},index ={0 :'apple',1 :'banana',2 :'durian'}, inplace=True)
# reset_index() 将旧索引添加为列,并使用新的顺序索引----> 多用于使用多个字段groupBy聚合之后
daily_rental = bikeDf_time.groupby(['year','month','day','weekday','day_type'])['count'].sum().reset_index()
# 在多重分组时,若不想生成复合索引,使用参数as_index=False, 即可将分组字段作为列来呈现(与上面效果一样)
daily_rental = bikeDf_time.groupby(['year','month','day','weekday','day_type'],as_index=False)['count'].sum()
# 使用reset_index(drop=True)将原索引删除,但不将原索引作为新的列插入数据中,一般用于排序之后
data_gr = data_gr.sort_values(by='count', ascending=False).reset_index(drop=True)
df删除列/se删除列(还有其它的?numpy怎么删?)
# list删除元素(三种方法: remove、del、pop)
li = ['A', 'B', 'C']
li.remove('B')
del li[1]
li.pop(2) # 参数是下标
# numpy删除列(一种方法: delete)
a = np.arange(12).reshape(3, 4)
print('第一个数组:')
print(a)
print('未传递Axis参数。在插入之前输入数组会被展开。')
print(np.delete(a, 5)) # 不会影响原数据a
print('删除第二列:')
print(np.delete(a, 1, axis=1)) # 不会影响原数据a
'''
第一个数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
未传递Axis参数。在插入之前输入数组会被展开。
[ 0 1 2 3 4 6 7 8 9 10 11]
删除第二列:
[[ 0 2 3]
[ 4 6 7]
[ 8 10 11]]
'''
# DataFrame删除列(三种方法:drop、del、pop, 后面两种只能一个一个的删)
df.drop('A', axis=1, inplace=True)
df.drop(['A', 'B'], axis=1, inplace=True)
del df['A']
df.pop('A')
# Series删除某个index(三种方法:drop、del、pop, 后面两种只能一个一个的删)
se.drop(['A', 'B'], inplace=True) # 注意这里不要带axis参数(只有axis=0,所以不用带)
del se['A']
se.pop('A')
df删除行、重复行
df = df.drop(0) # 索引名0重复被一并删除
# 根据条件删除行
df.drop(data[data['A4']==0].index, inplace=True)
copy = sheet1_shee2_merge.drop_duplicates('业务员编码', inplace=False) # 是直接在原来数据上修改还是保留一个副本
# 统计数据重复
# 结果为series, index为False和True, 值分别为不重复和重复的数量
print('sheet1订单号重复检查:')
print(sheet1Df['订单号'].duplicated().value_counts())
# 注意单词写法不一样:drop_duplicates与duplicated
# 所有列的值一样视为重复
print(data.duplicated().value_counts())
data.drop_duplicates(inplace=True)
# 过滤某列中重复值所在行
df = pd.DataFrame({'A': [12, 13, 12, 25, 60], 'B': [112, 112, 128, 112, 60]})
df = df[~df['B'].duplicated()] # df['B'].duplicated()是一个布尔类型Series
print(df)
'''
A B
0 12 112
2 12 128
4 60 60
'''
用字典创建series和dateframe的区别
前者:字典的key是创建index
后者:字典的key是创建columns
使用loc和iloc进行切片
# 省略方式取列的方法
df['B'] # 取一列, 为Series, 等同于df.loc[:, 'B']
df[['B']] # 取一列, 为Dateframe, 等同于df.loc[:, ['B']]
df[['A', 'B']] # 取多列
# 唯一可以省略方式取行的方法:必须使用冒号(其它方式使用loc或iloc)
df[2:4] # 参数是行索引号,结果为2行(等同于df.iloc[2:4, :])
df['20220806': '20220808'] # 行名,,结果为3行 (等同于df.loc['20220806': '20220808', :])
# 注意:上面行名必须是字符串类型才有这种效果!!!否则就视为使用索引号
# loc方法(注意loc中的参数冒号都是取头又取尾,不管index是不是默认或数值型)
df.loc[:, ['A', 'C']]
df.loc['20220806']
df.loc['20220806', 'A':'C']
df.loc['20220806': '20220808', ['A', 'C']]
df.loc['20220806', ['A', 'C']]
df.loc[['20220806', '20220808'], ['A', 'C']]
# iloc方法(行索引和列索引都可以不连续)(注意iloc中的参数冒号都是取头不取尾)
df.iloc[2:4, 0:2]
df.iloc[[2, 4], [0, 2]]
df.iloc[1, 1]
# loc和iloc的全部列冒号缺省情况
df.loc['b':'e'] # 等同于df.loc['b':'e', :], 是包含e行的
df.iloc[1:4] # 等同于df.iloc[1:4, :], index从1到3,不包含4
# Series与DataFrame类似,不同之处是iloc和loc只有一个参数
se[:2] # 等同于se.iloc[:2]
# 注意若se索引名不是采用默认索引,使用数字很有可能报错,所以下面这样方式不要随便使用!!!
se.loc[:2] # 注意这里的2是行号,取值时是包含2的,若行号与索引号不一致,例如排序之后的,取到的行数不确定
df布尔过滤
布尔过滤的原理:通过布尔型Series中的True对应的index来进行匹配,即同时用到了Series中的index和values
- 过滤某些行:通过某列的条件判断形成布尔型Series :se,然后再通过df[se]达到条件过滤的目的。其中se的长度与df行数一致,且se.index与df.index一致;(其实df[se]是df.loc[se, :]的简写形式)(也可以通过df.iloc[li, :]来达到目的, 其中li为布尔类型list,长度与行数一致)
- 过滤某些列:通过某行的条件判断形成布尔型Series :se,然后再通过df.loc[:, se]达到条件过滤的目的。其中se的长度与df的列数一致,且se.index与df.columns一致。(也可以通过df.iloc[:, li]来达到目的, 其中li为布尔类型list, 长度与列数一致)
# 布尔过滤原理的验证
df = pd.DataFrame({'A': [12, 13, 12, 25], 'B': [112, 112, 128, 122]}, index=[0, 2, 3, 4])
se = pd.Series([False, True, False, True]) # 使用默认索引
print(df[se]) # 报错
se = pd.Series([False, True, False, True], index=[0, 2, 3, 4])
print(df[se])
'''
A B
2 13 112
4 25 122
'''
print(df[df.A > 0]) # A列大于0的值
# print(df[df['A'] > 0]) # 也可以这样写
'''
A B C D
2020-01-01 0.105586 -0.173702 0.360643 -1.866179
2020-01-06 2.790227 0.053426 -1.123202 0.573211
'''
print(df[(df.A > 0) & (df.B < 0)]) # 多个条件过滤:需用小括号包裹,且只能用& | ~
# 注意这里与df[df.A > 0]的区别
# 注意:若条件判断语句不是对某一列进行判断,返回值不会将不满足行过滤掉
print(df[df > 0]) # 获取所有大于0的值,小于0设为NaN
'''
A B C D
2020-01-01 0.021760 0.467921 NaN 0.442172
2020-01-02 NaN NaN NaN NaN
2020-01-03 NaN NaN NaN 0.421954
2020-01-04 NaN NaN NaN 0.254046
2020-01-05 0.970615 1.234028 1.920165 0.802954
2020-01-06 NaN NaN NaN NaN
2020-01-07 NaN 0.292532 NaN NaN
'''
# 经试验,作判断前不需要确保没有空值,两种结果一样
#data_year = data[(data['Year'].notnull()) & (data['Year'] >= 1998) & (data['Year'] <= 2008)]
data_year = data[(data['Year'] >= 1998) & (data['Year'] <= 2008)]
# 也可以使用between
data_year = data[data['Year'].between(1998, 2008)]
# 若要排除两头边界,即大于1998且小于2008, 加上参数inclusive=False
data_year = data[data['Year'].between(1998, 2008, inclusive=False)]
# 使用isin()方法进行是否在列表中的过滤
df['E'] = ['one', 'one', 'two', 'three', 'four', 'three', 'two']
print(df[df['E'].isin(['two', 'four'])])
# isin()参数也可是Series
# isin()没有isnotin()这样表达,只能通过前面加~来实现
df_normal = data[~data['target'].isin(df_except['target'])]
# 过滤某列中重复值所在行
df = df[~df['B'].duplicated()]
# 字符串类型的模糊查询
copy2 = copy.loc[copy['业务员名称'].str.contains('张|赵')]
copy3 = copy.loc[copy['业务员名称'].str.startswith('张')]
copy3 = copy.loc[copy['业务员名称'].str.endswith('三')]
copy4 = copy.loc[copy['学号'].str.isnumeric()] #是否为数值型的字符串
copy5 = copy.loc[copy['英文名'].str.islower()] #所有字符是否为小写,isupper为是否为大写
# 对列进行过滤:过滤值全一样的列
data = data.loc[:, data.nunique() != 1]
字符串类型字段抽取特征-str.extract
# se.str.extract能抽取一组或多组正则表达式匹配的字符串,根据括号()决定返回几组匹配字符串
# expend=True(默认),不管几组,都返回一个DataFrame;expand=False,若是一组返回Series,多组返回一个DataFrame
# 字符串前面加“r”是为了防止字符转义。如果字符串中出现“\t”,不加“r”的话“\t”就会被转义,而加了“r”之后“\t”就能保留原有的样子
extract_df = data['auth_capital'].str.extract(r'([\d\.,]+)(.*)', expand=True)
se = pd.Series(['注册资本:5,946.67万元人民币', '注册资本:5946.67万港币'])
extract = se.str.extract('([0-9.,]+)(.*)') # 这里点号不用\貌似也不影响
print(extract)
'''
0 1
0 5,946.67 万元人民币
1 5946.67 万港币
'''
'''
正则表达式:
. 匹配任何字符
* 前面出现的正则表达式匹配0次或多次
+ 前面出现的正则表达式匹配1次或多次
? 前面出现的正则表达式匹配0次或1次
\d [0-9]
\w [A-Za-z0-9]
\. 加转义符,表示真正意义的点号
[] 匹配括号中的任意一个字符
() 匹配封闭括号中正则表达式,并保存为子组,如:f(oo|u)bar
'''
# \d+(\.\d*)? 表示简单浮点数,即任意个十进制数字,后面跟一个可选的小数点,然后跟0或多个十进制数
缺失数据处理
df['one'].isnull()
df['one'].notnull()
df['one'].sum() # 若有缺失数据(NaN),求和时视为0,这里不会报错
# 查看数据缺失情况
# 方法一
print(df.info())
# 方法二(推荐)
null_se = df.isnull().sum() # 生成一个Series
null_se = null_se[null_se > 0] # 过滤不为空的字段
print(null_se)
# 方法三:1表示不含缺失值,2表示含缺失值(notnull()结果只有True和False)
# 1表示不含缺失值,2表示含缺失值(notnull()结果只有True和False)
print(df.notnull().nunique())
df.fillna(0)
# 对某一列进行处理
df['A2'] = df['A2'].fillna(1)
df['A7'] = df['A7'].fillna(df['A7'].mean())
# pad/fill 填充方法向前
# bfill/backfill 填充方法向后
df.fillna(method='pad', inplace=True)
df.dropna() # 默认axis=0,按行删除,若一行中有NaN,删除该行
df.dropna(axis=1) # 按列删除
df.dropna(inplace=True, thresh=10) #thresh表示一行(axis=1为列)中最少非缺失值数量,小于此值就删除
df.dropna(how="any") #how="any"(默认)表示只要含有缺失值就删除,how="all"表示全部是缺失值才删除
df = df.dropna(subset=['A']) # 删除某一列是空值的行
# 等同于
df = df[df['A'].notnull()]
# 利用方法replace
df.replace({1000: 10, 2000: 20})
data['chol'].replace(564,246,inplace=True)
# 把小于0的值当做缺失值,先把其变成NaN
for col_name, value in data.dtypes.iteritems():
if value != 'object':
data[col_name] = data[col_name].map(lambda x: np.nan if x < 0 else x)
- 使用KNN填充缺失值
# KNNImputer需要sklearn0.22及以上版本才有
# 升级anaconda中的sklearn: pip install -U scikit-learn==0.22
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=10)
data_imputer = imputer.fit_transform(data) # 输出为numpy类型,需要重新赋值
data = pd.DataFrame(data_imputer, columns=data.columns)
# 由于KNN填充缺失值方式会把所有数据都转成float, 因此需要重新定义数据集数据类型
def define_type(data):
# float: rectal_temperature, nasogastric_reflux_PH, packed_cell_volume, total_protein, abdomcentesis_total_protein
float_col_list = ['rectal_temperature', 'nasogastric_reflux_PH', 'packed_cell_volume', 'total_protein', 'abdomcentesis_total_protein']
for col_name in data.columns:
if col_name in float_col_list:
data[col_name] = data[col_name].astype(float)
else:
data[col_name] = data[col_name].astype(int)
define_type(data)
- 使用拉格朗日插值方式填充缺失值
from scipy.interpolate import lagrange
#s为列向量,n为被插值的位置,k为取前后的数据个数,默认为5
def ployinterp_column(s, n, k=5):
y = s[list(range(n-k, n)) + list(range(n+1, n+1+k))] #取数
y = y[y.notnull()] #剔除空值
return lagrange(y.index, list(y))(n)
null_se = data.isnull().sum()
null_se = null_se[null_se > 0] # 获得含缺失值的列
for col_name in null_se.index:
for i in data.index:
if (data[col_name].isnull())[i]: #如果为空即插值
# 使用loc对data重新赋值
data.loc[i, col_name] = ployinterp_column(data[col_name], i)
-
数据分析空处理规则:
-
NaN -- 默认值(政策、法规、规范)
-
NaN -- 序列型用中位数(median)、数值型用均值(mean)、类别型用众数(mode, 注意其结果为Series, 需要加[0]) (序列型:如疾病的严重程度,一般分为轻1、中2、重度3,数字大小有严重等级关系)(类别型:如血型,一般分为A、B、O、AB四个类型,用1-4表示,数字大小没有等级关系)
data['小区房屋出租数量'] = data['小区房屋出租数量'].fillna(data['小区房屋出租数量'].mean()) data['地铁站点'] = data['地铁站点'].fillna(data['地铁站点'].median()) data['出租方式'] = data['出租方式'].fillna(data['出租方式'].mode()[0])
-
NaN -- 向前、向后(数据波动有规律)
-
NaN -- replace(明显错误)
-
NaN -- 逼近值(拉格朗日opp、牛顿插值oop)
-
NaN -- 删除(少于5%,不一定)行、列 (利用参数thresh(有效数据的最低要求), 5%需要手动计算一下)
-
异常数据处理
# 方式一:用箱线图方式去除异常点
def check_exception(se):
Q1 = se.quantile(0.25)
Q3 = se.quantile(0.75)
IQR = Q3 - Q1
up = Q3 + 1.5 * IQR
down = Q1 - 1.5 * IQR
me = se.median()
se_ex = se[(se > up) | (se < down)]
if len(se_ex) > 0:
se = se.map(lambda x: me if x > up or x < down else x)
print(se.describe())
return se
for i in range(1, 73):
col_name = 'F%d' % i
data[col_name] = check_exception(data[col_name])
# 方式二:用2倍或3倍标准差方式处理
for i in range(15, 20):
col_name = 'A%d' % i
mean = data[col_name].mean()
std = data[col_name].std()
data[col_name] = data[col_name].map(lambda x: mean if x > mean + 2 * std or x < mean - 2 * std else x)
# 方式三:根据箱线图判定异常值
df_A10_A13 = data.loc[:, 'A10':'A13']
df_A10_A13.plot.box()
for i in range(10, 14):
col_name = 'A%d' % i
mean = np.mean(data[col_name])
data[col_name] = data[col_name].map(lambda x: mean if x > 50 else x)
数据相关性
Pearson相关系数
- |r|<=0.3 不存在线性相关
- 0.3 < |r| < 0.5 低度线性相关
- 0.5 < |r| <=0.8 显著线性相关
- |r| > 0.8 高度线性相关
数据深度克隆
df.copy(deep=true)
df.pipe/df.apply/se.map
- df.pipe与se.map的区别
def adder(ele1,ele2):
return ele1+ele2
df=df.pipe(adder,20) # 方法1通过pipe调用操作函数,并设置叠加值
# df+=20 # 方法2直接将df叠加值即可,pandas最新管道用法
print('管道将DataFrame的所有元素叠加20:')
#通过map函数仅对指定列运算
myArray=np.random.randn(5,3)
print(myArray)
df = pd.DataFrame(myArray,columns=['col1','col2','col3'])
df=df['col1'].map(lambda x:x*100)
- df.apply(aggfunc) axis = 0(默认),按列处理;axis=1,按行处理,将一行作为一个Series传入aggfunc中
- se.map(aggfunc) 它是Series的函数,aggfunc只能传入一个参数
# 灵活使用自定义函数
def get_hospital_name(hospital_no):
flag = str(hospital_no)[:2]
if flag == '51':
return '甲'
if flag == '52':
return '乙'
return '丙'
data['hospital_name'] = data['hospital_no'].map(get_hospital_name)
统计
df.pct_change()
df.cov(df2)
df.corr(df2)
分组聚合过滤
df.aggregate(np.sum) # 可以简写为df.agg
# 分组
print(df.groupby('Team').groups) # 注意df.groupby('Team')结果显示的是对象内存
grouped = df.groupby('Year') # 其结果是一个元组(groupby_name, df_group),它可以用for进行遍历
#查看分组
print(grouped.get_group(2014)) # 获取某个组别数据
# 根据多个字段分组
grouped = df.groupby(['Year', 'Month'])
# 聚合(aggregate) - 先分组,再聚合
# 使用numpy标准函数:若使用一个函数(且不带中括号),生成一个Series, 否则生成一个DataFrame,列名为函数名,如sum,mean
print(grouped['Points'].agg(np.mean))
# 等同于
print(grouped['Points'].mean()) # 使用的是Pandas函数
# 多个列
print(grouped[['Points', 'Height']]).agg(np.mean)
# 多种集合函数
print(grouped['Points'].agg([np.sum, np.mean, np.std]))
# 字典方式
print(grouped.agg({'Points': np.mean}))
# 注意与上面的区别:上面生成一个Series, 字典方式和带中括号方式都是生成一个DataFrame
print(grouped['Points'].agg([np.mean])) # 带中括号就会生成DataFrame
# 使用字典方式实现不同字段使用不同聚合函数(包含自定义函数)
# 注意这里的区别:groupby后直接agg,不用取某一个字段
df_gr = data.groupby('No').agg({'Close': cal_perc_fun, 'Volume': np.sum, 'High': np.amax, 'Low': [np.amin, np.amax] }) # 其中cal_perc_fun为自定义函数,也可以写成lambda函数
# 生成的df_gr字段,若没有一个字段多个函数,直接就是字段名;否者为复合字段名,为元组形式,如:
#MultiIndex([('Close','cal_perc_fun'),('Volume','sum'),('High','amax'),('Low','amin'),( 'Low','amax')])
# 取值方式:df_gr[('Low', 'amin')]
# 注意上面的聚合函数若用numpy函数,就为np.***; 若为panda函数就需要加引号
df_gr = data.groupby('No').agg({'Volume': np.sum, 'High': 'max', 'Low': 'first'})
# 在多重分组时,若不想生成复合索引,使用参数as_index=False, 即可将分组字段作为列来呈现
data_gr = data.groupby(['id03', 'id04', 'id05'], as_index=False)['id00'].count()
# 等同于
data_gr = data.groupby(['id03', 'id04', 'id05'])['id00'].count().reset_index()
# 取分组后每组中的第一个不为NaN值(first())
df_e = data.groupby('HEAT_NAME').first().loc[:, 'E1':'E15']
# 灵活使用自定义聚合函数
# 注意:若分组后,只对一个量进行聚合操作,传过来的就是一个Series
def lived_ratio(se):
se_lived = se[se == 1]
return len(se_lived) / len(se) * 100
data_gr = data.groupby('hospital_name')['outcome'].agg(lived_ratio)
# 计算RFM值例子
def R_value(se):
se = se.sort_values(ascending=False) # 不会影响调用函数的数据源
se.index = range(len(se)) # 也可以不用重新定义index, 下面se[0]换成se.iloc[0]
latest_date = pd.to_datetime(se[0], format='%Y-%m-%d %H:%M:%S')
now_date = pd.to_datetime('2019-12-31 23:59:59', format='%Y-%m-%d %H:%M:%S')
delta = now_date - latest_date
return delta.total_seconds() / (24 * 3600) # 返回相隔小时数
data_gr = data.groupby('买家会员名').agg({'订单付款时间': [R_value, np.size], '买家实际支付金额': np.sum})
data_gr.columns = ['R-最近消费时间间隔', 'F-消费频率', 'M-消费金额']
# 使用自定义函数
print(grouped['Points'].transform(lambda x: (x-x.mean())/x.std * 10))
# 过滤
print(df.groupby('Team').filter(lambda x: len(x) > 3)) # 返回三次以上参加的队伍
透视表
'''
step4:数据透视表操作,每个地区的业务员分别赚取的利润总和与利润平均数
'''
pivot_table = pd.pivot_table(sheet1_shee2_merge, index='地区名称', columns='业务员名称', values='利润', aggfunc=[np.sum, np.mean])
print('每个地区的业务员分别赚取的利润总和与利润平均数透视表:')
print(pivot_table)
print('********************************************************************')
'''
sum ... mean
业务员名称 倪仲涛 刘仕祯 卞思宝 吴雯龙 尚庆龙 ... 陈国聚 陈恩泽 高鹏 麦豪 黄佳成
地区名称 ...
上海 NaN NaN NaN NaN NaN ... NaN 30.0 200.0 780.0 360.0
北京 NaN NaN 420.0 NaN NaN ... NaN NaN NaN NaN NaN
广州 300.0 170.0 NaN NaN 1200.0 ... NaN NaN NaN NaN NaN
深圳 NaN NaN NaN 330.0 NaN ... 3200.0 NaN NaN NaN NaN
[4 rows x 40 columns]
'''
# 或者将values属性放在aggfunc中
pivot_table = pd.pivot_table(df, index='month', columns='type', aggfunc={'flag': [np.sum, len]}) # 统计个数用len
# 多个值
pivot_table = pd.pivot_table(df, index='month', columns='type', aggfunc={'flag': [len, np.sum], 'flag2': sum}) #求和既可以用np.sum也可以用sum
# 使用fill_value参数填充NaN
# 当希望将id05列与id15列不同的值的组合方式都呈现出来,可以这两列分别放入参数index和columns中
data8 = data2.pivot_table(values='target', index='id05', columns='id15', aggfunc='std', fill_value=0)
'''
id15 45c48c c4ca42 cfcd20
id05
07078a 30.833425 0.000000 29.330063
15dfea 5.432277 0.000000 4.345821
336669 33.132657 10.297472 31.500252
e4c2e8 25.179980 0.000000 25.510223
'''
# 注意与将id05与id15放在一起的区别
data8 = data2.pivot_table(values='target', index=['id05', 'id15'], aggfunc='std', fill_value=0)
'''
target
id05 id15
07078a 45c48c 30.833425
cfcd20 29.330063
15dfea 45c48c 5.432277
cfcd20 4.345821
336669 45c48c 33.132657
c4ca42 10.297472
cfcd20 31.500252
e4c2e8 45c48c 25.179980
cfcd20 25.510223
'''
crosstab
# 分布统计不同赛季输和赢的场数
knicks2 = pd.crosstab(knicks.season, knicks.win)
'''
win L W
season
07-08 59 23
08-09 50 32
09-10 53 29
10-11 40 42
11-12 30 36
'''
哑变量
# 当值只有两类时
df['win_flag'] = df['win'].map({'L': 0, 'W': 1})
# 当值有多类时
def dummies(data, col_name_list):
for col_name in col_name_list:
# 类别大于2类的列做哑变量处理
if len(data[col_name].value_counts()) > 2:
tempDf = pd.get_dummies(data[col_name], prefix=col_name)
data = pd.concat([data, tempDf], axis=1)
data.drop(col_name, axis=1, inplace=True)
return data
按区间切割赋值
- pd.cut
bins = [0, 1932, 2345, 10000]
groups = ['低', '中', '高']
# 每一组默认左开右闭,若想改为左闭右开,加参数include_lowest=True, right=False
Dataframesale['分组'] = pd.cut(df['利润'], bins, labels=groups, include_lowest=True, right=False)
print(df)
print('********************************************************************')
'''
地区名称 利润 分组
0 上海 1970.0 中
1 北京 1820.0 低
2 广州 2720.0 高
3 深圳 8330.0 高
'''
- pd.qcut
# 将Age字段按照等宽间距划分为5等分,并统计输出每个区间的人数。
se1 = pd.qcut(full['Age'], q=5, labels=['L1', 'L2', 'L3', 'L4', 'L5'])
print('将Age字段按照等宽间距划分为5等分,并统计输出每个区间的人数:', se1.value_counts())
连接
rs = pd.merge(left, right, on='id')
rs = pd.merge(left, right, on=['id', 'subject_id'])
# 若连接的表之间有相同字段,可以通过参数suffixes加后缀,默认为['_x', '_y']
df_mer = pd.merge(df_2015, df_2016, on=['Month', 'Day', 'Hour'], suffixes=['_2015', '_2016'])
rs = pd.merge(left,right,on='subject_id',how='left')
# left/right/outer(全连接)/inner 默认为inner
a= pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3', 'K4', 'K5'], 'A': ['A0', 'A1', 'A2', 'A3', 'A4', 'A5']})
b = pd.DataFrame({'key': ['K0', 'K1', 'K9'],'B': ['B0', 'B1', 'B2']})
c=a.merge(b,how='outer') # 不写on参数,默认是按照相同列名作为连接键
print(c)
'''
key A B
0 K0 A0 B0
1 K1 A1 B1
2 K2 A2 NaN
3 K3 A3 NaN
4 K4 A4 NaN
5 K5 A5 NaN
6 K9 NaN B2
'''
# 有无on参数的区别
df1 = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'c']})
df2 = pd.DataFrame({'A': [1, 2, 4], 'B': ['a', 'b', 'd']})
df3 = pd.merge(df1, df2, how='outer') # 求并集
df4 = pd.merge(df1, df2, on='A', how='outer')
print(df3)
print(df4)
'''
A B
0 1 a
1 2 b
2 3 c
3 4 d
A B_x B_y
0 1 a a
1 2 b b
2 3 c NaN
3 4 NaN d
'''
合并
ignore_index设置为True生成连续索引
rs = pd.concat([one,two], keys=['x','y'], ignore_index=True)
#使用keys把特定的键与每个碎片的DataFrame关联起来
#ignore_index设置为True生成连续索引,一般在axis=0时使用
rs = pd.concat([one,two], axis=1) #横向合并 注意多个df一定要用[]
rs = one.append(two)
rs = one.append([two, one, two])
文件IO
df = pd.read_csv('temp.csv', index_col=['S.No']) # 使用某列作为索引
df = pd.read_csv('temp.csv', dtype={'Salary': np.float64}) # 更改某列的数据类型
# 使用header指定标题行,使用names自定义列名; 若文件不含标题行,使用header=None
df = pd.read_csv('temp.csv', names=['a', 'b', 'c'], header=0)
# skiprows跳到指定行数开始显示
df = pd.read_csv("temp.csv", skiprows=2)
# 若第1行是标题,想跳过第2、3行,skiprows用range赋值
df = pd.read_csv("temp.csv", header=0, skiprows=range(1, 3))
# 解析日期时间列
# 根据实际最好指定day在前,还是在后(默认),否则可能day和month解析反了
df = pd.read_csv(DATA_PATH, parse_dates=["DATE_TIME"], dayfirst=True)
# 或使用索引号
df = pd.read_csv(DATA_PATH, parse_dates=[0], dayfirst=True)
# 读取多个文件
def read_data():
file_names = os.listdir('./data/')
file_names = [item for item in file_names if item.endswith('.csv')]
df = pd.DataFrame()
for i in range(len(file_names)):
df_temp = pd.read_csv('./data/' + file_names[i])
df_temp['No'] = file_names[i].split('.')[0]
df = pd.concat([df, df_temp], ignore_index=True)
return df
时间序列
datelist = pd.date_range('2020/5/21', periods=5, freq='M')
print(datelist) # 注意它是一个序列,不是Series
'''
DatetimeIndex(['2020-05-31','2020-06-30','2020-07-31','2020-08-31','2020-09-30'],dtype='datetime64[ns]',freq='M')
'''
# 注意DatetimeIndex类型的基本属性:year/month/day/hour/minute/second, 注意这里与下面datatime64类型字段读取年、月、日等属性用法的区别
print(datelist.year) # 结果是Int64Index
print(datelist.month)
# 日期时间类型字段
# 应用案例
def R_value(se):
se = se.sort_values(ascending=False)
se.index = range(len(se))
# 一般若文件保存的是日期时间格式,使用read_csv会自动解析为datatime64类型(通过dtype查看)
# 否则使用下面语句转换
latest_date = pd.to_datetime(se[0], format='%Y-%m-%d %H:%M:%S')
# 对单个时间转换后为Timestamp类型
now_date = pd.to_datetime('2019-12-31 23:59:59', format='%Y-%m-%d %H:%M:%S')
delta = now_date - latest_date
return delta.total_seconds() / (24 * 3600)
# 提取日期时间类型字段中的年、月、日
df['Year'] = df['Joined date'].dt.year
df['Month'] = df['Joined date'].dt.month
df['Day'] = df['Joined date'].dt.day
df['Week'] = df['Joined date'].dt.week
df['Hour'] = df['Joined date'].dt.hour
df['Minute'] = df['Joined date'].dt.minute
df['Second'] = df['Joined date'].dt.second
# 两列datetime类型相减求相距天数dt.days和秒数dt.total_seconds(),注意:前者没括号,后者有!!!
df = pd.DataFrame({'name': list('ABCDE'), 'date': pd.date_range('2020/5/21', periods=5, freq='D'), 'date2': pd.date_range('2020/9/21', periods=5, freq='M')})
delta_days = (df['date2'] - df['date']).dt.days
delta_seconds = (df['date2'] - df['date']).dt.total_seconds()
# 或者使用pd.Timedelta
delta_seconds = (df['date2'] - df['date']) / pd.Timedelta(seconds=1)
# 字符串类型日期时间格式化,下面两种方式都是可行的
start_time = pd.datetime.strptime('2023-08-22 08:00', '%Y-%m-%d %H:%M')
end_time = pd.to_datetime('2023-08-22 17:00', format='%Y-%m-%d %H:%M')
delta = (end_time - start_time) / pd.Timedelta(hours=1)
# 或使用pd.to_timedelta
delta2 = (end_time - start_time) / pd.to_timedelta(1, unit='h')
middle_time = start_time + pd.Timedelta(hours=delta*0.5)
print(start_time)
print(end_time)
print(delta)
print(delta2)
print(middle_time)
#2023-08-22 08:00:00
#2023-08-22 17:00:00
#9.0
#9.0
#2023-08-22 12:30:00
# pd.Timedelta和pd.to_timedelta的区别:后者可以传入Series,结果为TimedeltaIndex
print(pd.to_timedelta(np.arange(5), unit='s'))
#TimedeltaIndex(['0 days 00:00:00', '0 days 00:00:01', '0 days 00:00:02','0 days 00:00:03', '0 days 00:00:04'],dtype='timedelta64[ns]', freq=None)
DataFrame赋值时省略index=和columns=**
df = pd.DataFrame([['2018', 88], ['2019', 99]], ['year', 'data'], ['000', '001'])
# 参数中第一个是value, 第二个是index, 第三个是columns
God will send the rain when you are ready.You need to prepare your field to receive it.