Python之数据分析
Numpy
Numpy是Python的一种开源的数值计算扩展。可以用来存储和处理大型矩阵,比Python自身的嵌套列表结构(nested list structure)要高效得多。在实际工作中直接使用情况较少。
数组
numpy.array()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import numpy as np
a = np.array([1, 2, 3, 4])
print(a)
# 输出为:array([1, 2, 3, 4])
# 和 列表 相似,但处理效率要高很多
a[0] = 5
# 得到数组 array([5, 2, 3, 4])
a + 1
# 数组中每个元素都+1,得到数组 array([6, 3, 4, 5])
# 减法、乘除法同理
b = np.array([[1, 2, 3], [4, 5, 6]])
# 可以生成多维数组查看数组中存储的数据类型:
dtype
1
2print(b.dtype)
# 输出为:int32
Pandas
Pandas是基于Numpy的一种工具,是为了解决数据分析任务而创建的。Pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
1 | import pandas as pd |
两种数据结构
Series (一维)
1 | s1 = pd.Series([1, 2, 3, 4]) |
DataFrame (二维)
1 | d = { |
得到如下二维表格:
name | sex | age | |
---|---|---|---|
0 | qinlu | male | 18 |
1 | lulu | male | 18 |
2 | qinqin | female | 25 |
1 | df = pd.DataFrame([1, 2, 3, 4]) # 效果类似 Series |
得到如下二维表格:
0 | |
---|---|
0 | 1 |
1 | 2 |
2 | 3 |
3 | 4 |
1 | df = pd.DataFrame([[1, 2, 3, 4], [3, 4, 5, 6]], columns=list('abcd')) |
得到如下二维表格:
a | b | c | d | |
---|---|---|---|---|
0 | 1 | 2 | 3 | 4 |
1 | 3 | 4 | 5 | 6 |
DataFrame的列的顺序默认为提供的顺序。如下操作可以调整列的顺序:
1 | df = df[['b', 'd', 'c', 'a']] |
得到如下二维表格:
b | d | c | a | |
---|---|---|---|---|
0 | 2 | 4 | 3 | 1 |
1 | 4 | 6 | 5 | 3 |
1 | df = pd.DataFrame(d) |
Series通过reset_index()
可以转换为DataFrame
Series原有的index也是变为一个列
~
代表反转
1 | ~ (df.age > 19) #等价于 df.age <= 19 |
筛选
1 | df[df.age == 18] # 筛选出 年龄=18的 所有行(筛选 结果为True 的行) |
增删
pandas中增删的运算效率低,尽量不使用。
del
:将指定列从存储空间中删除
1 | del df.loc[df.age == 19, 'age'] |
drop()
:删除指定项时返回的是视图,数据仍然在存储空间中1
2df.drop('age', axis=1)
# axis=1表示列,axis=0表示行(默认)
Pandas基础命令速查表
读取/写入文件
读取
1 | import pandas as pd |
read_csv()
的参数:
文件名
encoding
:指定编码python默认以utf-8编码读取文件。如果报错无法正确显示文件内容,可以将编码改为gbk等进行读取。
sep
:指定分隔符,默认为逗号,
有的文件由于不标准等原因,采用其他分隔符如
\t
,可通过指定分隔符优化显示。sep='\s+'
:将tab和多个空格都当成一样的分隔符。parse_dates
:将数据解析为日期parse_dates = True
:尝试解析所有可能为日期类型的列;parse_dates = [1, 2]
:尝试解析给定列为日期类型的列。parse_dates = [[1, 2]]
:尝试解析给定列为日期类型的列,并将这些列聚合成为1个列names
指定列名,默认为文件中的第一行。如果自定义列名,文件中的第一行将作为第一行数据显示。
1 | df.info() # 概览数据 |
写入
to_csv()
函数
T
:转置表格df.T
shape
:获取数据框的行数和列数(元组存储)如
df.shape
:可能得到如(10,5)
(10行5列)columns
:获取所有列index
:查看索引信息set_index()
:设置索引1
crime = crime.set_index('Year')
筛选函数
sort_values()
:按值排序,默认升序参数:
by
:指出排序依据的字段1
2
3
4
5
6
7df.sort_values(by = 'avg')
# 下述方式效果类似(返回的是avg的有序数组)
df.avg.sort_values()
# 根据多个条件排序
df.sort_values(by = ['avg', 'city']) # 排序根据unicode而不是拼音,如果想要按照拼音顺序,需要将中文和英文字母关联(可新建一个城市首字母缩写的列)ascending
:默认为True(升序)inplace
:是否用排序后的数据集替换原来的数据,默认为False
sort_index()
:按索引排序rank()
:给出排名参数:
ascending
:默认为True(升序)method
'average'
(默认,如前5个人分数相同,则排名为**(1+5) / 2** (最大值和最小值的加权平均数))'min'
(取最小值)'max'
'first'
(不考虑并列情况)
1
df['rank'] = df.avg.rank(ascending=False, method='min') # 按照平均工资排名,将排名作为新的列加入表格
unique()
:去除重复项1
df.city.unique()
value_counts()
:对Series的每个值进行计数并且排序1
df.education.value_counts()
describe()
1
df.avg.describe()
得到:
1
2
3
4
5
6
7
8
9count 5031.000000
mean 17.111409
std 8.996242
min 1.500000
25% 11.500000
50% 15.000000
75% 22.500000
max 75.000000
Name: avg dtype: float64count
:计数mean
:平均数std
:标准差
describe()
的统计内容会自动忽略空值count()
max()
min()
last()
mean()
对整个数据框求平均值,结果为按列(默认)分别计算平均值,得到一个Series;
对Series求平均值,结果则为一个平均数
参数:
axis=0
(0为按列,1为按行)sum()
median()
:中位数std()
:标准差var()
:方差cumsum()
:累加pandas.cut()
:分类统计1
2
3
4
5
6# 4等分
pd.cut(df.avg, bins=4, label=list('abcd'))
# 自定义分隔区间
# 为了能够全部包含需要分类的数据区间,最后的值一般一个取极大的值
pd.cut(df.avg, bins=[0, 5, 10, 20, 30, 100], label=['0~5', '5~10', '10~20', '20~30', '30~100'])pandas.qcut()
isin()
1
2# 找到英格兰(England)、意大利(Italy)和俄罗斯(Russia)的射正率(Shooting Accuracy)
euro12.loc[euro12['Team'].isin(['England', 'Italy', 'Russia']), ['Team', 'Shooting Accuracy']]可以和
~
配合使用,达到不存在的函数isnotin()
的效果。idxmax()
:返回请求轴上第一次出现最大值(不包括``NA/null`)的索引参数:
axis=0
:0对应行,1对应列
聚合函数
MySQL难以完成分组排序。
groupby()
:分组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
32df.groupby(by = 'city')
# 得到如下已存入内存的提示:
# <pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000012CF16A41C8>
# 当前分组下,对各分组中的各列进行统计
df.groupby(by = 'city').count()
# 当前分组下,各分组中各列的最大值
df.groupby(by = 'city').max()
# 当前分组下,对各分组中某列的最大值
df.groupby(by = 'city')['avg'].max()
# 多字段分组
df.groupby(by = ['city', 'workYear']).mean()
for k, v in df.groupby(by=['city']):
# 打印每个分组中的元素数量
print(len(k[1]))
# 打印分组的依据元素(具体的city)
print(k)
# 打印分割线
print('**' *10) # 20个星号
# 打印分组中元素的详细内容
print(v)
# 打印同个城市中,最高薪资和最低薪资的差值
print(max(v.avg) - min(v['avg']))
多表关联
1 | import pandas as pd |
merge()
函数 —— 根据具体的键值相当于SQL中的JOIN
1
2
3
4
5
6
7
8
9
10
11
12
13position.merge(right = company, how='left', on='companyId')
# 更改列名
# 方式一:单独更改一个列名
col = list(company.columns)
col[0] = 'id'
company.columns = col
# 方式二:利用rename()更改指定列名
company.rename(columns={'0':'id', '1':'xxx'}, inplace=True)
pd.merge(left=position, right=company, how='inner', left_on='companyId', right_on='id')参数:
left/right
:关联的表how
:关联的方式inner
(默认)outer
left
right
on
:具体的键值(在两个表中对应列名相同时使用)left_on/right_on
:具体的键值(在两个表中对应列名不同时使用,关联之后保留不同名的列)
join()
:根据索引(行号)按照索引关联很有局限性。
- 只要两个表列名不同,不加任何参数就可直接使用
- 如果两个表有重复的列名,需要制定
lsuffix
,rsuffix
参数 - 默认左外连接(LEFT JOIN)
1
df1.join(df2)
concat()
:堆叠类似于SQL中的UNION,多个表暴力堆叠,新的表包含多个表的所有列,没有对应列值的表项为NaN。
参数:
axis
:- 0:上下堆叠(默认,相同列名会合并 )
- 1:左右堆叠
1
pd.concat([company, position], axis=1)
区分辨别
Series级别可以直接输入索引标签进行筛选;DataFrame级别,需要通过loc
进行筛选。
1 | position.groupby(by=['city', 'education']).mean().avg['上海'] |
不借助groupby
,如何设置多重索引:
1 | # sort_values 用于排序,set_index 将列设置为索引 |
agg()
函数agg()
是聚合函数,得到DataFrame。参数有:func
:实现某种统计功能的函数axis=0
1
2
3# grouped_user 是通过groupby('user_id')得到的 DataFrameGroupBy 对象
user_life = grouped_user['order_dt'].agg(['first', 'last']) # order_dt列是时间格式,first 和 last 函数能分别得到最早和最迟的时间
user_life.head()
文本函数
str.count()
:统计指定字符出现的次数1
position.positionLabels.str.count('分析师')
str.find()
:查找指定字符出现的位置1
position.positionLabels.str.find('数据')
str[1:-1]
:去除表示列表的方括号1
position.positionLabels.str[1:-1]
str.replace("'", "")
:去除列表中元素的单引号1
position.positionLabels.str[1:-1].str.replace("'", "")
str.startwith()
:筛选以指定字符开头的字符串1
euro12['Team'].str.startwith('G')
str.split()
:拆分单元格内容1
2
3df2 = df['某一列'].str.split('指定分隔符', expand=True)
# 默认会删去分隔符
# expand=True 能让拆分后的单元格内容 由 列表类型 变为 DataFrame(默认为False)str.extract()
:提取单元格中所需内容参数:
pat
:字符串或正则表达式- 只有用
()
包裹的部分才会被保留 - Python中使用正则表达式命名捕获组语法为:
(?P<name>Expression)
,即将name
作为列名
- 只有用
flags
:整型expand
:是否将提取的内容由列表类型变为DataFrame
1
2# 提取 '建筑年代'列 的数字,剥离 年建
df3['建筑年代'] = df3['建筑年代'].str.extract('(\d+)年建')str(xxx)
:将其他类型的数据(如时间)转为字符串
时间相关
Python中的 datetime模块
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
40from datetime import datetime
from datetime import timedelta
now = datetime.now() # 获取当前日期和时间
print(now)
print(datetime.date.today()) # 获取当前日期
delta = now - datetime(2017,6,27,10,10,10,10) # 获取日期相差值
print(delta) # 得到 xxdays, xx:xx:xx.xxx
print(delta.days) # 72天数
print(delta.seconds) # 秒数
print(delta.microseconds) # 微秒数
# 2061年?我们真的有这一年的数据?创建一个函数并用它去修复这个bug
# x是一个 YYYY-mm-dd格式 的日期
def fix_century(x):
year = x.year
if year > 2000:
year -= 100
return datetime.date(year, x.month, x.day)
text = '2019-09-07'
y = datetime.strptime(text, '%Y-%m-%d') # 把字符串转为日期
print(y)
# 当前日期的前后n日期
print(datetime.date.today()+timedelta(days=-1))
# 获得某一日期的月初和月末
text='2019-09-07'
month_first=datetime.strptime(text[:8]+'01','%Y-%m-%d')
print(month_first) #输出结果为:2019-09-01 00:00:00
month_end=datetime.strptime(text[:5] + str(int(text[5:7])+1) + '-01','%Y-%m-%d')+timedelta(days=-1)
print(month_end) # 输出结果为:2019-09-30 00:00:00datetime.now()
:获取当前日期和时间(和MySQL的NOW()
相同)datetime.date.today()
:获取当前日期datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
:生成指定的时间datetime.date(year, month, day)
:构造日期格式,但是数据类型不变datetime.strptime()
:把字符串转为日期(和pandas的to_datetime函数作用相同)
Pandas中的 to_datetime函数
pandas.to_datetime()
:将指定列的 数据类型 转换为 指定格式的 时间类型1
2
3
4
5
6
7import pandas as pd
crime['Year'] = pandas.to_datetime(crime['Year'], format='%Y')
date=['2017-6-26', '2017-6-27']
print(pd.to_datetime(date))
#输出结果为:DatetimeIndex(['2017-06-26', '2017-06-27'], dtype='datetime64[ns]', freq=None)format参数和数据可视化-模块 datetime 中含义一致
astype()
可以将时间类型的数据更改精度。
pandas.to_datetime()
默认转换的时间类型为datetime64[ns]
,精度为ns(纳秒)级。1
2df['datetime'] = pd.to_datetime(df['datetime'], format='%Y%m%d')
df['month'] = df['datetime'].values.astype('datetime64[M]') # 将精度转换为M(月份)级别,日子一律会更改为1日上述代码,使用
values
将Series内的数值以ndarray
或ndarray-like
的形式返回,因为datetime64[ns]
无法直接转换为datetime64[M]
类型
pandas.date_range()
:创建时间序列参数:
start
:起始时间end
:结束时间periods
:当只声明了起始时间或结束时间时,需要告知时间范围freq
:时间频率(默认为'D'
(Calendar day frequency))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import pandas as pd
from datetime import datetime
pd.date_range(start=datetime.now(), periods=6, freq='B') # 'B'的含义为 营业日频率(business day frequency)
# 得到:
# DatetimeIndex(['2020-07-13 20:34:18.990072', '2020-07-14 20:34:18.990072',
# '2020-07-15 20:34:18.990072', '2020-07-16 20:34:18.990072',
# '2020-07-17 20:34:18.990072', '2020-07-20 20:34:18.990072'],
# dtype='datetime64[ns]', freq='B')
pd.date_range(start='20130809',periods=8,freq='SM') # 'SM'的含义为 月中和月末(semi-month end frequency)
# 得到:
# DatetimeIndex(['2020-07-15', '2020-07-31', '2020-08-15', '2020-08-31',
# '2020-09-15', '2020-09-30', '2020-10-15', '2020-10-31'],
# dtype='datetime64[ns]', freq='SM-15')
pd.date_range(start=datetime.now(),periods=5,freq='2H20min') # 时间间隔140min
pd.date_range(start='20190623', periods=10, freq='1D10U') # 时间间隔1天10微秒
numpy.timedelta64()
Numpy允许两个Datetime值相减,这个操作产生一个带有时间单位的数字。由于NumPy的核心没有物理量(物理单位)系统,因此**创建了
timedelta64
数据类型以补充datetime64
**。1
2
3
4
5x = np.datetime64('2017-08-03') - np.datetime64('2017-07-15')
# 得到的结果是 x = numpy.timedelta64(19,'D') # 第一个参数为数字,第二个参数为单位
# 利用timedelta64() 消除单位
x / np.timedelta64(1, 'D') # 得到的结果是19.0
以日期时间序列为索引的最多日期相差天数
1
(df.index.max() - df.index.min()).days
以日期时间序列为索引的数据框中一共有多少个月
1
2d = df.resample('BM').last()
len(d.index) # 'DatetimeIndex' object has no attribute 'count',因此不能使用count函数计数把字符串转成日期
重新采样
重新采样指将时间序列从一个频率转换为另一个频率的过程。
降采样(向下采样)
高频时间序列变为低频,时间粒度变大。如:原有100个时间点,变为10个时间点
升采样(向上采样)
低频时间序列变为高频,时间粒度变小。,
同频之间的切换
比如W-WED(weekly on Wednesday 每周三)转换到W-FRI(每周五)
**函数resample()
**:
不同于groupby关注特征值的分组操作,它在以时间序列为索引的数据框中使用,对时间索引分组操作来聚合运算。
参数:
rule
:所需采样频率的字符串字符串 含义 B 营业日频率
(business day frequency)C 自定义营业日频率
(custom business day frequency)D 日历日频率
(calendar day frequency)W 每周一次频率
(weekly frequency)M 月末频率
(month end frequency)SM 月中和月末频率
(semi-month end frequency (15th and end of month))BM 营业月末频率
(business month end frequency)CBM 自定义月末频率
(custom business month end frequency)MS 月初频率
(month start frequency)SMS 月初和月中频率
(semi-month start frequency (1st and 15th))BMS 营业月初频率
(business month start frequency)CBMS 自定义营业月初频率
(custom business month start frequency)Q 季度末频率
(quater end frequency)BQ 营业季度末频率
(business quater end frequency)QS 季度初频率
(quater start frequency)BQS 营业季度初频率
(business quater start frequency)A / Y 年末频率
(year end frequency)BA / BY 营业年末频率
(business year end frequency)AS / YS 年初频率
(year start frequency)BAS / BYS 营业年初频率
(business year start frequency)BH 营业小时频率
(business hour frequency)H 小时频率
(hourly frequency)T / min 分钟频率
(minutely frequency)S 秒频率
(secendly frequency)L / ms 毫秒频率
(milliseconds)U / us 微秒频率
(microseconds)N 纳秒频率
(nanoseconds)axis=0
:需要采样的轴向(0为行方向)closed
:在降采样时,各时间段哪一侧(left/right)是闭合的。label
:在降采样时,index用区间的左界值还是右界值。
空值相关
1 | import pandas as pd |
去重
1 | # 查找重复值(默认:第一次遇到不会当作重复值,将第二次开始遇到的值作为重复值看待) |
移动数据:shift()
参数:
periods
:移动的幅度(可正可负),默认为1。只移动数据,不移动索引,移动之后没有对应值的,赋值为``NaN`
freq
:用于时间序列索引,默认为None
DateOffset
timedelta
time rule string
按照参数值移动时间索引,不移动数据值。
axis
:指定移动方向0
:上下移动(默认)1
:左右移动
apply()
apply()
可以将函数应用到所有的行/列上进行处理。
apply()
中的匿名函数``lambda中的
x`到底指代什么:对某一列操作时:
每一个x依次对应各行的具体值
对多个列(axis**=0**,默认)操作时:
每一个x依次对应每一列
对多个列(axis**=1**)操作时:
每一个x依次对应每一行(依次对每个单元格进行操作)
1 | # Series级别 |
匿名函数lambda中条件语句的使用
1
2
3# 条件语句为 A if 条件 else B
# 即 满足条件则输出A,否则输出B
x.apply(lambda x:'1' if x>= 0 else '0')
将apply()
应用到聚合函数上
获取不同城市中工资水平前五的信息:
1 | def func(x, n): |
Pandas中map()
, apply()
和applymap()
的区别
参考:Pandas中的map(), apply()和applymap()的应用
它们的区别在于应用的对象不同:
map()
是一个Series的函数,将一个自定义的函数应用于Series结构中的每个元素
apply()
将函数作用于DataFrame中的每个行或者列
applymap()
将函数作用于DataFrame中的每个元素
数据透视
pivot_table()
1 | position.pivot_table(index=['city', 'education'], |
注:数据透视不适合用来去重
连接数据库
- 安装相关工具包
pip install pymysql
注意:如果是安装了Anaconda,要在Jupyter Notebook中载入pymysql包,则必须在Jupyter Notebook中打开Python3或Ternimal的页面输入上述代码。另外单独安装的Python是独立于Anaconda的,两者下载的工具包可能无法通用。
连接
1
2
3
4
5
6
7
8conn = pymysql.connect(
host = 'localhost', # 本地数据库,也可输入 '127.0.0.1'
user = 'root',
password = '123456',
db = 'test',
port = 3306,
charset = 'utf8'
)查询数据库
1
2
3
4
5
6cur = conn.cursor() # 获取游标
cur.execute('SELECT * FROM courses') # 执行SQL语句
data = cur.fetchall() # 调取SQL语句执行后的结果(元组的形式)
for d in data:
print(d[0], d[1], d[2])提交对数据库的修改
1
conn.commit()
关闭数据库的连接
1
2cur.close()
conn.close()
使用Pandas对数据库进行操作
1 | import pymysql |
Matplotlib
Matplotlib是一个Python的2D绘图库。它以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形。我们通常使用该库将数据可视化,更形象直观地暴露问题所在。
Scikit-learn
Scikit-learn(Sklearn)是机器学习中常用的第三方模块,对常用的机器学习方法进行了封装,是建立在Numpy、Scipy、Matplotlib之上,简单高效的数据挖掘和数据分析工具。
Jupyter
Jupyter是一个Web应用程序,可以用来编写Python代码、图表展示、数值处理和转换、数值模拟和统计建模等各种任务。
Jupyter Notebook
快捷键 | 功能 |
---|---|
Tab | 自动补全 |
Shift + Tab | 显示函数示例 |
Shift + Enter | 执行当前,并跳转至下一Cell |
Ctrl + Enter | 执行当前,并留在当前Cell |
Anaconda
Anaconda是一个免费开源的Python和R语言的发行版本,用于计算科学(数据科学、机器学习、大数据处理和预测分析),Anaconda致力于简化包管理和部署。Anaconda的包使用软件包管理系统Conda进行管理。
Anaconda3默认包含Python 3.7,但是用户可以创建虚拟环境来使用任意版本的Python包。
Anaconda Navigator
Anaconda Navigator是包含在Anaconda中的图形用户界面,用户可以通过Anaconda Navigator启动应用,在不使用命令行的情况下管理软件包、创建虚拟环境和管理路径。
Anaconda Navigator包含如下应用:
- JupyterLab
- Jupyter Notebook
- QtConsole
- Spyder
- Glueviz
- Orange
- Rstudio
- Visual Studio Code
问题
利用apply(),使用自定义函数更新DataFrame每行单元格的内容,但是得到的不是DataFrame对象
1 | # # 定性 用户18个月的消费 |
原因:
自定义函数的返回值是列表,而不是Series,导致apply()之后的结果不是一个DataFrame对象。
解决方法:
将自定义函数的返回值转换为Series对象
1
2--snip--
return pd.Series(status, index=cdnow_purchase.columns) # index参数是原DataFrame对象的各列列名