前言
最近经常用到 Pandas 来进行一些数据分析,由于自己对 Pandas 使用的不熟练,也经常一时想不起 Pandans 的某些用法。所以,在这里简单记录下自己平时用到的自定义函数。
这里没有对 Pandas 的介绍,会持续更新的,仅是个人记录而已,不喜勿喷~
使用环境
jupyter notebook
导入所需包
import pandas as pd
1. 列出某列中各个值依次出现的序号
1.1 构建数据
# 构建数据 每次比赛第一名的获得者
data = {'编号': ['01', '02', '01', '01', '02'],
'姓名': ['小明', '小红', '小明', '小明', '小红'],
'参赛时间': ['20170804', '20180804', '20190804', '20200804', '20210804']}
df = pd.DataFrame(data)
df
1.2 需求
求各个第一名获得者分别在不同年份是第几次获得第一名?
1.3 代码
#count_if函数
def countif(line, data, base):
'''
line:apply 函数 axis=1 代表当前行数据
data:需要统计的指定列的数据
base:df 中需要统计的列索引
'''
value = line[base] # 获得当前行指定列的值
end = line['index']+1 # 获得当前行的索引值
return data.iloc[:end].value_counts()[value] # 返回指定列中当前行值出现的次数
# reset_index 重置索引
stat_df = df['编号'].reset_index()
df['第几次获得第一名'] = stat_df.apply(countif, axis=1, args=(stat_df['编号'], '编号',))
df
想着 pandas 会不会有直接满足本需求的函数,找了半天没找到。Excel 中 countif() 可以实现本需求,但是实际数据有15万行,一运行就崩了,不知是我电脑不行,还是 Excel 不熟悉。就想着用 Pandas 来实现,又不想写 for 循环,那样的话感觉完全体现不了 Pandas 的优势(啥优势咱也说不清楚,可能是不像 Pandaser -- 使用 Pandas 的人,哈哈~)。最后想起了 apply 函数,但是其函数只能获得当前行或者当前列的值,无法获得当前行的索引(或许是我不知道吧)。
故此,只能用 reset_index() 重置索引,将常数索引变成一列。
df['编号'].reset_index() 运行的效果:
然后,在 countif() 函数 中利用当前行的 index 获取指定列的数据片段,通过 value_counts() 来统计频数从而获得当前行指定列的值 value 出现的次数。
注:
- 关于 countif() 函数中的 data 我也取了好几次值,从 “df” 到 “stat_df”,最后发现还是“stat_df['编号']”最好,一次次缩小了 data 。
- 其实,我这里好像还是少了一步,应该先对“参赛时间”进行排序的,不过这个不是重点。
用上面的方法去跑15W行的数据,居然跑了近2个小时,我感觉我应该没有做到一个 Pandaser 的做法。然后想着用 groupby() 分下组,然后用 apply() + 自定义函数(内部调用 rank())。写着写着,发现根本就用不着 apply(),GROUPBY 自身就有 rank()。这太好了!只是 GROUPBY.rank() 没有 numeric_only 参数,需要将"参赛时间"转为时间类型,因为时间类型其实底层也是数值,所以能用 GROUPBY.rank()。
df['参赛时间'] = pd.to_datetime(df['参赛时间'])
df['第几次'] = df.groupby('编号')['参赛时间'].rank()
df
15W行数据轻松搞定!!!
2. 统计频数和频率
2.1 构建数据
沿用 1.1 的数据。
2.2 需求
1) 某人获取第一名的频数和频率。 2) 所有人获取第一名的“频数/总数”和频率。
2.3 代码
# 统计一列的数据分布
def frequency_distribution(data, values=None, is_count=False, dropna=True):
# data: Series 数据
# values:list 需要统计的值
# is_count:bool 是否输出总数,形如 频数/总数
# dropna: value_counts 本身的参数,空值是否删除
if isinstance(values, list):
stat_values = values
elif values is None:
stat_values = list(data.dropna().unique())
else:
stat_values = [values]
# count 统计总数
if dropna:
count = data.count()
else:
count = len(data)
frequency = data.value_counts(ascending=True, dropna=dropna) # 频数
relative_frequency = data.value_counts(normalize=True, dropna=dropna) # 频率
for value, number in frequency.items():
# 此处的 key 是 需要统计的值
if value in stat_values:
if is_count:
print('{0}\t{1}/{2}\t{3:4.2f}%'.format(value, number, count, relative_frequency[value] * 100))
else:
print('{0}\t{1}\t{2:4.2f}%'.format(value, number, relative_frequency[value] * 100))
stat_values.remove(value) # 移除已统计的
# 可能统计的值 不在 value_counts 的结果里
for value in stat_values:
if value not in frequency.keys():
if is_count:
print('{0}\t{1}/{2}\t{3:4.2f}%'.format(value, 0, count, 0))
else:
print('{0}\t{1}\t{2:4.2f}%'.format(value, 0, 0))
注:
- 这里我没有将结果数据封装为 DataFrame,因为 print 就能满足我的要求。
- 这里百分位的小数是取的两位,若需,请自行修改,或者设置成一个参数。 关于上述两个问题,本人较懒,你可以自行修改。
代码修改记录:
- 20210804 15:35 新增 dropna 参数
2.4 示例
2.4.1 获取第一名的所有人的频数和频率
frequency_distribution(df['姓名'])
2.4.2 获取第一名的所有人的频数和频率,并显示总数
frequency_distribution(df['姓名'], is_count=True)
2.4.3 “小红”获取第一名的频数和频率,并显示总数
frequency_distribution(df['姓名'], '小红', is_count=True)
2.4.4 所有参赛人员的获取第一名的频数和频率,并显示总数,即使某人没有获得第一名也显示
此时,只需获得所有参赛人员的名单即可。
frequency_distribution(df['姓名'], ['小红','小明','小张'], is_count=True)
3. 基本统计项
3.1 代码
def mean_std_ci(data, ci=0.95):
"""
计算 均值 标准差 95%CI 中位数 范围(最小, 最大)
"""
describe = data.describe()
mean = describe['mean']
std = describe['std']
count = describe['count']
# 95% 置信区间
tscore = stats.t.ppf(1-(1-ci)/2, count - 1)
lower = mean - (tscore*std/(count**0.5))
upper = mean + (tscore*std/(count**0.5))
median = describe['50%']
min_ = describe['min']
max_ = describe['max']
Q1 = describe['25%']
Q3 = describe['75%']
print('Count:{0:.0f}'.format(count))
print('Mean ± SD:{0:.2f} ± {1:.2f}'.format(mean, std))
print('95% CI:{0:.2f} to {1:.2f}'.format(lower, upper))
print('Median:{0:.2f}'.format(median))
print('Range (min, max):{0:.2f}, {1:.2f}'.format(min_, max_))
print('Q1~Q3:{0:.2f}~{1:.2f}'.format(Q1, Q3))
print("\n==================================\n")
IQR = Q3-Q1
print(f'IQR:{IQR:.2f}')
# 异常值
in_range_1 = Q1 - 1.5*IQR
in_range_3 = Q3 + 1.5*IQR
out_range_1 = Q1 - 3*IQR
out_range_3 = Q3 + 3*IQR
print(f'內限:{out_range_1:.2f}~{in_range_1:.2f}, {in_range_3:.2f}~{out_range_3:.2f}')
print(f'外限:<{out_range_1:.2f}, >{out_range_3:.2f}')
in_data = data[((data>=out_range_1) & (data<in_range_1)) | ((data>in_range_3) & (data<=out_range_3))]
out_data = data[(data<out_range_1) | (data>out_range_3)]
print(f'温和异常值:{list(in_data)}')
print(f'极端异常值:{list(out_data)}')