普通视图

发现新文章,点击刷新页面。
昨天以前Allen's Blog

利用Flask与pyecharts搭建Dashboard

作者 Allen Jia
2019年8月15日 01:12

概述

当处理一些较为灵活的数据时,团队内不同角色的同事会有自己对数据的关注点,所以,这就要求数据分析师不能只出一个“死”报告了事儿,而需要的是一个可以让同事们去探索,去解决自己关注问题的”活“报告——Dashboard。本文就一起来探讨下,利用FlaskPyecharts搭建局域网内Dashboard的方法,其中Flask用来提供Web应用框架,Pyecharts用来解决交互式可视化的需求。

功能需求

  • 可视化图像可交互
  • 可以根据用户需求,从数据库中筛选不同的数据进行可视化

Dashboard框架

mE1fkd.png

如上图所示,我们依据数据的流向确定了Dashboard的框架,并列出了在不同过程中的所需知识:

  • 依据Dashboard中用户的需求(可选),从数据库中提取数据,并对数据进行预处理,以方便后续进行可视化;
  • 利用Pyecharts对提取的数据进行可视化;
  • 利用Flask将Pyecharts生成的可视化图像嵌入到HTML模板中,并利用Javascript丰富前端的动作、处理事件,利用Ajax实现前后端交互等。

两个关键点

  • 在Flask框架中使用Pyecharts

这在Pyecharts的官方文档中写的非常详细,可以戳Flask 模板渲染查看示例。

  • 如何处理Jinja2 和 JavaScript 语法的冲突

为了快速搭建Dashboard,免去一些前端配色、布局等烦恼,我们一般会挑选一个HTML模板,而Jinja2就是一个模板渲染引擎,它的语法中,使用一对双大括号标记变量,这与JavaScript的语法标记会有冲突,所以,如果你使用Jinja2进行模板渲染的同时使用了JavaScript,就要进行语法冲突处理

最终效果

mEB0bR.gif

mEBg2D.gif

致谢&参考

源码

Github链接:Flask+Pyecharts定制Dashboard

除以上两点之外,在源码中,还实现了以下功能:

  • 多个可视化图像的联动
  • 多级菜单联动选择
  • 前后端交互
  • 表格数据排序与搜索

希望能对有相同需求的你有所帮助,欢迎留言,一起讨论!

知识共享许可协议

本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

Python中查找字符串中第n次出现某字符的位置

作者 Allen Jia
2019年7月23日 11:12

概述

在处理字符串时,有时需要从字符串中提取出第n次出现某字符的位置,比如说想在字符串'abcdabdcsas'中找到第2次出现'ab'的索引,但Python String提供的find函数只能Return the lowest index in S where substring sub is found,所以,自己动手,丰衣足食:joy:

本文分为两部分:

  • 解决上述问题的两种方法及运行效率对比
  • 延伸:
    • 出现某字符的全部索引
    • 最后一次出现某字符的索引

两种方法

  • 常规思路

先用find函数查找得到第一次出现的索引,然后将字符串在该索引处做切片,再进行查找,以此类推,直到查询到第n次。

1
2
3
4
5
6
7
8
9
10
11
def finding_nemo_1(String,Substr,times): 
'''
在String中查找Substr出现第times次的索引。
'''
s = time.time()
nemo = 0
for i in range(1,times+1):
nemo = String.find(Substr,nemo) + 1
if nemo == 0:
return -1
return (nemo-1, time.time()-s)
  • 利用split切分和列表

将String按照Substr切分times次,代码如下所示,计算很简单。

1
2
3
4
5
def finding_nemo_2(String,Substr,times): 
s = time.time()
String_list = String.split(Substr,times)
nemo = len(String) - len(String_list[-1]) - 1
return (nemo, time.time()-s)

我们对以上两种方法进行了对比,如下图所示,第二种方法的优势随着字符串长度的增长而更加明显。

第二种方法,巧就巧在将运行效率低的for循环替换成了split,在对长度为250e6的字符串进行查找时,运算时间缩短了近77%。

延伸

很容易将以上两种方法改写成获取某字符出现的全部索引函数。

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
#方法一
def finding_all_nemo_1(String,Substr):
start = 0
end = len(String) - 1
nemo = 0
nemo_list = []
while nemo != -1:
nemo = String.find(Substr,start,end)
nemo_list.append(nemo)
start = nemo + 1
nemo_list.remove(-1)

return nemo_list

#方法二
def finding_all_nemo_2(String,Substr):
count = 0
nemo_list = []
String_list = String.split(Substr)

for i in range(len(String_list)-1):
element = String_list[i]
count += len(element)
nemo = i + count
nemo_list.append(nemo)
return nemo_list

那获取某字符串最后一次出现的位置,有没有更便捷的方法呢?

  • 注意:find可以很快的找到第一次出现的位置

所以,我们将字符串反转,再用find就可以很容易求得索引了。

1
last_index = -(String[::-1].find(Substr) + 1)

如果你也有其他的方法,欢迎留言,一起讨论~

知识共享许可协议

本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

机器学习中不平衡数据的预处理

作者 Allen Jia
2019年4月19日 09:33

概述

最近在处理自主招生的数据,对于某一个确定的高校来说,录取的人数远远小于未录取的人数,换言之,就是录取类的数据量远小于未录取类的数据量,这就是不平衡数据,虽然在机器学习中不平衡数据的处理不是难点,但这也是我们不得不去考虑的问题,那在本篇文章中,我们便来一起探讨下有哪些处理不平衡数据的技巧

什么是不平衡数据

不平衡数据,顾名思义,就是指在收集到的数据中各个分类之比并非为1:1,在对不平衡数据的研究中,普遍认为不平衡意味着少数类所占比例在10%到20%之间,但实际上,这种现象可能会更严重,比如说:

  1. 每年有大约2%的信用卡用户存在欺诈;
  2. 某种情况的医学筛查,比如说美国的艾滋病得病率约为0.4%;
  3. 磁盘驱动器的故障率每年约1%;
  4. 在线广告的转化率约在10-3至10-6之间;
  5. 文章开头提到的高校录取率,比如某985院校的自主招生审核通过率在0.2%左右。

对于这种不平衡的数据来说,如果不进行数据预处理就应用机器学习算法进行训练,那么得到的模型只需要把所有的结果都预测为多数类那边,就能获得很高的准确率(比如说,对所有的学生都判定为审核未通过,那么准确率会达到1-0.2%=99.8%),但是这其实一点用都没有。

平衡数据大概像是这样:

不平衡数据大概像是这样:

处理不平衡数据

处理不平衡数据的思路比较简单,那就是想办法让数据平衡,我们可以简单得分为以下几类:

  • 更改数据集中各分类数据的量,使他们比例匹配——常用方法有采样、数据合成;
  • 更改数据集中各分类数据的权重,使他们的量与权重之积匹配——常用方法为加权;
  • 不修改数据集,而是在思路上将不平衡数据训练问题转化为一分类问题或者异常检测问题(少数类就像是存在于多数类中的异常值)。

更多更详细研究范畴的分类可以查看这篇论文A Survey of Predictive Modelling under Imbalanced Distributions

那接下来,我们分别看一下各个方法在python中的具体实施。

采样

采样就是通过减少多数类数量的下采样(Undersampling)或增加少数类数量的上采样(Oversampling)方式,实现各类别平衡。针对两种采样方式,依赖不同的方法,比如说 随机、模型融合等,就会生成很多解决方案,可以看这篇综述Learning from Imbalanced Data的总结。

代码实现

有一个叫做imbalanced-learn的库,是专门针对不平衡数据设计的,其中包含很多上采样和下采样的函数,官方文档在这里。

示例:

1
2
3
4
5
6
7
8
9
10
from collections import Counter
from sklearn.datasets import make_classification
from imblearn.under_sampling import RandomUnderSampler
from imblearn.over_sampling import RandomOverSampler

#生成不平衡数据
X, y = make_classification(n_classes=2, class_sep=2,
weights=[0.1, 0.9], n_informative=3, n_redundant=1, flip_y=0,
n_features=20, n_clusters_per_class=1, n_samples=1000, random_state=10)
print('Original dataset shape %s' % Counter(y))

Original dataset shape Counter({1: 900, 0: 100})

1
2
3
4
#随机下采样
rus = RandomUnderSampler(random_state=42)
X_res, y_res = rus.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))

Resampled dataset shape Counter({0: 100, 1: 100})

1
2
3
4
#随机上采样
ros = RandomOverSampler(random_state=42)
X_res, y_res = ros.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))

Resampled dataset shape Counter({0: 900, 1: 900})

数据合成

随机上采样会反复出现一些样本,而导致过拟合;随机下采样则会造成一定程度的特征丢失,虽然这种方式比较简单,但现在计算机的计算能力越来越高,可接受的算法复杂度也越来越高,所以我们应该主要考虑模型训练的效果。

数据合成则是利用已有样本生成更多样本,其中常用的包括:

  • SMOTE,利用KNN生成新数据;

  • SMOTEC,可以合成分类数据,但数据集中至少要包含一条连续数据;

    如果数据集中全是分类数据的话,可以增加一列全为1的intercept列作为连续数据,合成数据之后,再将该列删除即可。

  • BorderlineSMOTE,与SMOTE的区别是,只为那些周围大部分是大众样本的小众样本生成新样本(因为这些样本往往是边界样本);

代码实现

1
2
3
4
5
6
from imblearn.over_sampling import SMOTE

#SMOTE合成
sm = SMOTE(random_state=42)
X_res, y_res = sm.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))

Resampled dataset shape Counter({0: 900, 1: 900})

更改权重

更改权重就是针对不同类别的数据设置不同的分错代价,即提高少数类分错的代价或降低多数类分错的代价,最终使各类别平衡。

常用的机器学习训练方法中,很多都提供了权重设置参数class_weight,可以手动设置该参数,但一般情况下只需要将其设置为balanced即可,模型会自动按照如下公式更新权重:

EpOrss.png

代码实现

1
2
3
4
5
6
7
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier

#逻辑回归平衡权重
clf_A = LogisticRegression(random_state=0, class_weight='balanced')
#随机森林平衡权重
clf_B = RandomForestClassifier(random_state=0, n_jobs=-1, class_weight="balanced")

一分类

这种方法比较适合极不平衡数据,或数据量比较小的数据集。

主要方法为OneClassSVM,官方文档在这里

代码实现

1
2
from sklearn import svm
clf = svm.OneClassSVM(nu=0.2, kernel="rbf", gamma=0.1)

异常检测我没怎么用过,所以就不赘述了,感兴趣可以戳这个链接Novelty and Outlier Detection

总结

我们可以依据数据量的大小和是否平衡将数据集分为四类,即平衡的大数据集,不平衡的大数据集,平衡的小数据集,不平衡的小数据集。最简单的就是平衡的大数据集,能达到非常高的准确率,最难的就是不平衡的小数据集,除了在平衡上下功夫之外,还需要很多诸如收集数据、特征工程之类的工作,但这并不是本篇文章的重点。

下面根据个人经验谈下针对如上几种数据集,如何选择文中涉及的这些方法:

  • 数据量较小的情况考虑用数据合成的方法,依据特征的类型(分类&连续数值)选择合适的方法。
  • 数据量还可以,但类别之间数量相差悬殊的时候考虑用一分类或者异常检测的方法。
  • 数据量还可以,而且类别之间数量相差不是特别悬殊的情况,考虑用采样或者更改权重的方法。

具体数据量的大小可以参考下图:

参考

  1. Learning from Imbalanced Classes
  2. A Survey of Predictive Modelling under Imbalanced Distributions

知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

利用python批量出报告

作者 Allen Jia
2019年4月4日 07:30

概述

最近单位需要批量输出报告,好在这些报告的整体模板相同,只有一些跟用户相关的信息需要替换。几千份的重复工作,还是交给Python去处理吧。

本文的技术路径为:利用docxtpl对word模板文件进行变量替换,然后利用 将word转换为pdf。

本文代码应用环境如下:

1
2
3
4
* Windows 7
* Python 3.7
* docxtpl-0.5.17

Word文档处理

安装

  • 主要用docxtpl库实现。官方文档

  • 直接pip install docxtpl即可,会自动安装依赖库:docxjinja2

模板文档准备

如下所示,将需要进行变量替换的位置用两对大括号括起来,并在其中添加变量名,这是后续能通过python识别替换的关键。

A23KQx.png

表格

表格的变量设置比较麻烦,可以查看官方github,里面有很多示例代码,随用随取。

设置表格变量的时候,有两个关键,一个是 ,一个是,其中行用字段tr(row)表示,列用字段tc(column)表示。

如下表中所示,我们通过构建列表循环 和 调用字典key的方式,对表格中的各个项进行填充。

A2GUqf.png

使用示例

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
from docxtpl import DocxTemplate,InlineImage
from docx.shared import Mm
import jinja2

#读取模板文档
tpl = DocxTemplate('自主招生大数据报告模板.docx')

#替换word中的变量
#字典中的key为变量名,value为要替换的值
context = {
'stu_id':123,
'stu_phone':13612341234,
'stu_province':'河北',
'stu_grade':'高二',
'stu_wenli':'理科',
'stu_school':'南宫中学',
'stu_university':'西安交通大学',
'stu_prize_1':'化学省一',
'image_1':InlineImage(tpl,'E:/wallpapers/1.jpg',width=Mm(120)),
'items' : [
{'university' : '西安交通大学', 'major' : '电气工程,能源动力','year':2018, 'prize' : '数学省一','nums':200 },
{'university' : '复旦大学', 'major' : '金融学', 'year':2018,'prize' : '数学省三','nums':20 },
{'university' : '北京理工大学', 'major' : '兵器学', 'year':2018,'prize' : '物理省二','nums':2 },
],
}

#输出
tpl.render(context)
tpl.save('output/自主招生报告.docx')

Word转PDF

通过Windows Com组件(win32com),调用Word服务(Word.Application),实现Word到PDF文件的转换。因此,要求该Python程序需要在有Word服务(可能至少要求2007版本)的Windows机器上运行。

参考:批量转换Word文件为PDF文件

1
2
3
4
5
6
7
8
9
10
11
12
13
import win32com.client

#WdSaveFormat指令,17表示转为PDF
#更多信息可以查看:https://docs.microsoft.com/zh-cn/office/vba/api/word.wdsaveformat
wdFormatPDF = 17
word = win32com.client.Dispatch('Word.Application')
#打开文档,绝对地址
doc = word.Documents.Open('F:\\0 zizhuzhaosheng\\docs\\test.docx')
#输出文档路径,绝对地址
doc.SaveAs('F:\\0 zizhuzhaosheng\\docs\\test.pdf', FileFormat=wdFormatPDF)
#退出
doc.Close()
word.Quit()

知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

第三周-Numpy和Pandas基础

作者 Allen Jia
2018年12月12日 02:30

A positive attitude causes a chain reaction of positive thoughts,events and outcomes.

Hi,同学们,上周我们主要对python的基础知识进行了学习,从完全不懂到写出第一段代码,从畏惧发怵到解决第一个代码问题,大家已经从小白迈出了python入门的第一步!你们都是最棒的!那请继续保持着这样的学习动力,趁热打铁,继续我们的课程吧!

本周开始,我们就进入到了项目二(P2)阶段的第二周,需要针对数据分析来学习两个重要的第三方库NumpyPandas,它们是python实现科学计算和数据处理的重要库,在以后的数据分析路上会经常用到,所以一定要掌握,并且还要熟练!

时间学习重点对应课程
第1周Python基础内容数据类型和运算符、控制流、函数、脚本编写
第2周Python数据处理内容Numpy & Pandas
第3周完成项目项目:探索美国共享单车数据
第4周项目修改与通过修改项目、查缺补漏、休息调整

对于非小白同学来说,本阶段内容不是很难,希望你们能在三周内完成并通过项目;

对于小白来说,本阶段可能是个挑战,请一定要保持自信,请一定要坚持学习和总结,如果遇到任何课程问题请参照如下顺序进行解决:

饭要一口一口吃,路要一步一步走,大家不要被任务吓到,跟着导学一步一步来,肯定没问题哒!那我们开始吧!

注:本着按需知情原则,所涉及的知识点都是在数据分析过程中必须的、常用的,而不是最全面的,想要更丰富,那就需要你们课下再进一步的学习和探索!

本周目标

  • 学完课程Numpy&Pandas
  • (可选)项目环境准备。

知识清单

Numpy

NumPy 简介

NumPyNumerical Python 的简称,它是 Python 中的科学计算基本软件包。NumPy 为 Python 提供了大量数学库,使我们能够高效地进行数字计算。更多可点击Numpy官网查看。

关于Numpy需要知道的几点:

  • NumPy 数组在创建时有固定的大小,不同于Python列表(可以动态增长)。更改ndarray的大小将创建一个新的数组并删除原始数据
  • NumPy 数组中的元素都需要具有相同的数据类型,因此在存储器中将具有相同的大小。数组的元素如果也是数组(可以是 Python 的原生 array,也可以是 ndarray)的情况下,则构成了多维数组。
  • NumPy 数组便于对大量数据进行高级数学和其他类型的操作。通常,这样的操作比使用Python的内置序列可能更有效和更少的代码执行

所以,Numpy 的核心是ndarray对象,这个对象封装了同质数据类型的n维数组。起名 ndarray 的原因就是因为是 n-dimension-array 的简写。接下来本节所有的课程都是围绕着ndarray来讲的,理论知识较少,代码量较多,所以大家在学习的时候,多自己动动手,尝试自己去运行一下代码。

创建ndarray

课程中所说的,创建一个秩为2的ndarray,实际上指的是创建一个2维的ndarray,并不是矩阵的秩。

  • 由python list创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
a = np.array([1, 2, 3])  # 1维数组
print(type(a), a.shape, a[0], a[1], a[2])

out:
<class 'numpy.ndarray'> (3,) 1 2 3

a[0] = 5 # 重新赋值
print(a)

out:
[5 2 3]

b = np.array([[1,2,3],[4,5,6]]) # 2维数组
print(b)

out:
[[1 2 3]
[4 5 6]]

print(b[0, 0], b[0, 1], b[1, 0])

out:
1 2 4
  • 由numpy内置函数创建
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
a = np.zeros((2,2))  # 创建2x2的全0数组
print(a)
[[ 0. 0.]
[ 0. 0.]]

b = np.ones((1,2)) # 创建1x2的全1数组
print(b)
[[ 1. 1.]]

c = np.full((2,2), 7) # 创建2x2定值为7的数组
print(c)
[[7 7]
[7 7]]

d = np.eye(2) # 创建2x2的单位矩阵(对角元素为1)
print(d)
[[ 1. 0.]
[ 0. 1.]]

d_1 = np.diag([10,20,30,50]) #创建一个对角线为10,20,30,50的对角矩阵
print(d_1)
[[10 0 0 0]
[ 0 20 0 0]
[ 0 0 30 0]
[ 0 0 0 50]]

e = np.arange(15) #创建一个一维的0-14的数组
print(e)
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]

e_1 = np.arange(4,10) #创建一个一维的4-9的数组
print(e_1)
[4 5 6 7 8 9]

e_2 = np.arange(1,14,3) #创建一个一维的1-13且以间隔为3的数组
print(e_2)
[ 1 4 7 10 13]

f = np.linspace(0,10,6) #创建一个一维的范围在0-10,长度为6的数组
print(f)
[ 0., 2., 4., 6., 8., 10.]
#各个元素的间隔相等,为(10-0)/(6-1) = 2,若不想包含末尾的10,可以添加参数endpoint = False

g = np.arange(12).reshape(3,4) #把arange创建的一维数组转换为3行4列的二维数组
print(g) #同样方法也适用于linspace等
[[ 0, 1, 2, 3], #注意:使用reshape转换前后的数据量应该相同,12 = 3x4
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]]

h = np.random.random((2,2)) # 2x2的随机数组(矩阵),取值范围在[0.0,1.0)(包含0,不包含1)
print(e)
[[ 0.72776966 0.94164821]
[ 0.04652655 0.2316599 ]]

i = np.random.randint(4,15,size = (2,2)) #创建一个取值范围在[4,15),2行2列的随机整数矩阵
print(i)
[[6, 5],
[5, 9]]

j = np.random.normal(0,0.1,size = (3,3))
#创建一个从均值为0,标准差为0.1的正态分布中随机抽样的3x3矩阵
print(j)
[[-0.20783767, -0.12406401, -0.11775284],
[ 0.02037018, 0.02898423, -0.02548213],
[-0.0149878 , 0.05277648, 0.08332239]]

访问、删除、增加ndarray中的元素

这里主要是提供了一些访问、更改或增加ndarray中某一元素的基础方法。

  • 访问&更改

类似于访问python list中元素的方式,按照元素的index进行访问或更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#访问某一元素,这里可以自己多尝试,摸索
print(np.arange(6)[3]) #访问一维数组的某一元素,中括号内填写index
3

print(np.arange(6).reshape(3,2)[1,1]) #访问二维数组的某一元素,中括号内填写[行,列]
3

print(np.arange(12).reshape(2,3,2)[0,1,1]) #访问三位数组中的某一元素,中括号内[组,行,列]
3

#更改某一元素,用 = 进行赋值和替换即可
a = np.arange(6)
a[3] = 7 #先访问,再重新赋值
print(a)
[0 1 2 7 4 5]
  • 删除

可使用np.delete(ndarray, elements, axis)函数进行删除操作。

这里需要注意的是axis这个参数,课程中只讲到了2维数据中,axis = 0表示选择axis = 1表示选择,但不能机械的认为0就表示行,1就表示列,注意前提2维数据中

在三维数据中,axis = 0表示组,1表示行,2表示列。这是为什么呢?提示一下,三位数组的shape中组、行和列是怎样排序的?

所以,axis的赋值一定要考虑数组的shape。

1
2
a = np.arange(12).reshape(2,2,3)
print(np.delete(a,[0],axis = 0)) #思考下,这里删除axis = 0下的第0个,会是什么结果呢?自己试一下

再有一点需要注意的是,如果你想让原数据保留删除后的结果,需要重新替换一下才可以。

1
2
3
4
5
6
7
8
9
10
11
a = np.arange(6).reshape(2,3)
np.delete(a,[0],axis = 0)
print(a)

array([[0, 1, 2],
[3, 4, 5]]) #原数据并未更改

a = np.delete(a,[0],axis = 0) #重新替换
print(a)

array([[3, 4, 5]]) #原数据已更改
  • 增加

往ndarray中增加元素的办法跟python list也很类似,常用的有两种:

  • 一种是添加(append),就是将新增的元素添加到ndarray的尾部

    • 语法为:np.append(ndarray, elements, axis)
    • 参数和delete函数一致,用法也一致,这里不再赘述
  • 一种是插入(insert),可以让新增元素插入到指定位置

    • 语法为:np.insert(ndarray, index, elements, axis)
    • 参数中就多了一个index,指示的是插入新元素的位置。

这里值得注意的是,不论是append还是insert,在往多维数组中插入元素时,一定要注意对应axis上的shape要一致。再一个就是,和delete一样,如果你想要更改原数据,需要用a = np.append(a,elements,axis)

ndarray切片

前面学了选择ndarray中的某个元素的方法,这里我们学习选择ndarray子集的方法——切片。

对于切片大家并不陌生,在list里面我们也接触过切片,一维的ndarray切片与list无异。需要注意的是,就是理解2维及多维ndarray切片。

  • 2维矩阵切片
1
2
3
4
5
6
7
8
9
10
11
12
13
a = np.arange(4*4).reshape(4,4)
print(a)
out:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
a[:,:-1]
out:
array([[ 0, 1, 2],
[ 4, 5, 6],
[ 8, 9, 10],
[12, 13, 14]])

这里可以看出,我们筛选了a矩阵中前三列的所有行,这是如何实现的呢?

切片的第一个元素:表示的是选择所有行,第二个元素:-1表示的是从第0列至最后一列(不包含),所以结果如上所示。

再看一个例子:

1
2
3
4
a[1:3,:]
out:
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])

筛选的是第2-3行的所有列。

  • 一个常用的切片

以列的形式获取最后一列数据:

1
2
3
4
5
6
a[:,3:]
out:
array([[ 3],
[ 7],
[11],
[15]])

以一维数组的形式获取最后一列数据:

1
2
3
a[:,-1]
out:
array([ 3, 7, 11, 15])

上面两种方法经常会用到,前者的shape为(4,1),后者为(4,)。

ndarray筛选

  • 选择ndarray的对角线

所用函数为np.diag(ndarray, k=N),其中参数k的取值决定了按照哪一条对角线选择数据。

默认k = 0,取主对角线;

k = 1时,取主对角线上面1行的元素;

k = -1时,取主对角线下面1行的元素。

思考:这个函数只能选择主对角线上的元素,那如果想要获取副对角线上的元素呢?

尝试自己搜索一下关键词numpy opposite diagonal寻找答案。

不建议你直接点getting the opposite diagonal of a numpy array

  • 提取ndarray中的唯一值

所用函数为np.unique(ndarray),注意unique也可以添加参数axis来控制评判唯一值的轴方向,不好理解可以看示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = [[0,1,2],
[3,4,5],
[0,1,2]]
print(np.unique(a)) #查看二维数组a中的唯一值
array([0, 1, 2, 3, 4, 5])

print(np.unique(a,axis = 0)) #查看a中的唯一行(也就是没有重复的行)
array([[0, 1, 2],
[3, 4, 5]])

print(np.unique(a,axis = 1)) #查看a中的唯一列
array([[0, 1, 2],
[3, 4, 5],
[0, 1, 2]])

print(np.unique(a[0])) #查看a中第一行的唯一值
array([0, 1, 2])
  • 通过布尔运算筛选

这里在中括号中添加筛选条件,当该条件的结果为True时(即满足条件时),返回该值。

1
X[X > 10] #筛选数组X中大于10的数据

这里需要注意的是,当输入多个筛选条件时,&表示与,|表示或,~表示非。

ndarray运算

  • 集合运算
1
2
3
np.intersect1d(x,y) #取x与y的交集
np.setdiff1d(x,y) #取x与y的差集,返回的是在x中且没在y中的元素
np.union1d(x,y) #取x与y的并集
  • 算术运算

我们可以通过+-*/np.addnp.substract np.multiplynp.divide来对两个矩阵进行元素级的加减乘除运算,因为是元素级的运算,所以两个矩阵的shape必须要严格一致。

上面涉及到的乘法是元素对应相乘,也就是点乘,那矩阵的叉乘呢?可以了解下numpy.matmul函数。

这里需要注意的是,课程中讲的“可广播”,其实指的就是A和B两个矩阵shape可能不一致,但是A可以拆分为整数个与B具有相同shape的矩阵,这样在进行元素级别的运算时,就会先将A进行拆分,然后与B进行运算,结果再组合一起就可以。这里的A就是“可广播”矩阵。

ndarray排序

我们使用np.sort()ndarray.sort()来对ndarray进行排序。

相同的是:

二者都可以使用参数axis来决定依照哪个轴进行排序,axis = 0时按照列排序,axis = 1时按照行排序;

不同的是:

np.sort()不会更改原数组;ndarray.sort()会更改原数组。

numpy迷你项目

这里涉及到一个概念,叫做数据标准化

数据的标准化(normalization)是将数据按比例缩放,使之落入一个小的特定区间。在某些比较和评价的指标处理中经常会用到,去除数据的单位限制,将其转化为无量纲的纯数值,便于不同单位或量级的指标能够进行比较和加权。

有很多种对数据进行标准化处理的方法,我们课程中选择的是利用数据均值和标准差进行标准化:

FJWC3F.png

对某一列中某一值进行标准化就是将该值减去该列的平均值,然后除以该列的标准差。标准化后的序列,均值为0,标准差为1,且无量纲。

标准化后的数据,没有量纲,方便计算和比较,在机器学习中的很多算法都需要将数据进行标准化。

但是基于本章的要求,我们主要是学习numpy的基本操作即可,具体的数据标准化还有算法可以之后在机器学习课程中学习。

这里需要注意的是:

  • np.random.permutation()

np.random.permutation(N) 函数会创建一个从 0 到 N - 1的随机排列的整数集。这个整数集也是ndarray类型。

1
2
np.random.permutation(5)
array([3, 1, 2, 4, 0])
  • 将数据集切分为训练集、测试集和交叉集。

这里的切分有两点隐形要求:

1.随机性,三个数据集中的数据必须是随机分配的;

2.三个数据集的合集必须为数据集。

考虑到上面学到的`np.random.permutation()` 函数,所以我们的思路可以是这样的:1. 使用`permutation()`函数,将数据集的行数当作N,这样就可以得到一个随机排列的行索引序列;2. 使用切片,将刚才的随机行索引序列,按照训练集、测试集和交叉集的比例`6:2:2`进行切分;3. 使用索引访问,获取切分后的数据,即`ndarray[index]`的方式。

Pandas

Pandas简介

Pandas 是 Python 中的数据操纵和分析软件包,它是基于Numpy去开发的,所以Pandas的数据处理速度也很快,而且Numpy中的有些函数在Pandas中也能使用,方法也类似。

Pandas 为 Python 带来了两个新的数据结构,即 Pandas Series(可类比于表格中的某一列)和 Pandas DataFrame(可类比于表格)。借助这两个数据结构,我们能够轻松直观地处理带标签数据和关系数据。

Series中各个元素的数据类型可以不一致,DataFrame也是如此,这与numpy的ndarray不同。

创建Pandas Series

可以使用 pd.Series(data, index) 命令创建 Pandas Series,其中data表示输入数据, index 为对应数据的索引,除此之外,我们还可以添加参数dtype来设置该列的数据类型。

示例:

1
2
3
4
5
6
7
8
9
import pandas as pd
pd.Series(data = [30, 6, 7, 5], index = ['eggs', 'apples', 'milk', 'bread'],dtype=float)

out:
eggs 30.0
apples 6.0
milk 7.0
bread 5.0
dtype: float64

data除了可以输入列表之外,还可以输入字典,或者是直接一个标量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#data输入字典
pd.Series(data = {'eggs':30,'apples': 6, 'milk':7, 'bread':5},dtype=float)

out:
apples 6.0
bread 5.0
eggs 30.0
milk 7.0
dtype: float64

#data输入某一标量
pd.Series(data = 7, index = ['eggs', 'apples', 'milk', 'bread'])

out:
eggs 7
apples 7
milk 7
bread 7
dtype: int64

访问和删除Series中的元素

  • 访问

访问Series中的元素有两种方法:

一种类似于从列表中按照索引访问数据,一种类似于从字典中按照key来访问value

下面看示例:

    FY9vQg.png

从上面代码里也能发现,Pandas提供的ilocloc分别对应着按索引访问和按key访问。

  • 修改

因为Series是可更改类型,若想更改其中某一项,只需访问它然后重新赋值即可。

  • 删除

可以使用 .drop() 方法删除 Pandas Series 中的条目。Series.drop(label) 方法会从给定 Series 中删除给定的 label。这个label可以是单个label或这是label组成的list。

FYPWKe.png

但需要注意的是,.drop()函数并不会修改原来的数据,如果你想要修改原数据的话,可以选择添加参数inplace = True或者是用原数据替换s = s.drop(label)

Series运算

和ndarray一样,Series也可以进行元素级的算术运算,也可以使用np中提供的各种运算函数,如sqrt()等等。

这里可以想一下,如果Series中包含字符串,然后再进行乘法会是什么结果?

可以回想下字符串的知识'*'*10的结果是什么?

创建DataFrame

我们使用pd.DataFrame(data, index, columns)来创建一个DataFrame。

其中:

data是数据,可以输入ndarray,或者是字典(字典中可以包含Series或arrays或),或者是DataFrame;

index是索引,输入列表,如果没有设置该参数,会默认以0开始往下计数;

columns是列名,输入列表,如果没有设置该参数,会默认以0开始往右计数;

示例:

FYkQDe.png

从上述代码中可以看出,字典d中的key被当作列名,value被当作dataframe中的数据。

思考:如果在上述代码中添加一个columns列,如df = pd.DataFrame(data=d,index = ['a','b'],columns = ['col_1','col_2']),会返回什么结果呢?

访问DataFrame中的元素

与访问Series中的元素类似,我们可以通过列表式索引访问,也可以通过字典式Key值访问。

  • 创建一个DataFrame

FYfMh6.png

  • 访问某一行

FYh15q.png

  • 访问多行

FY4lOe.png

  • 访问某一列

FY4nW6.png

  • 访问多列

FY4tYt.png

使用df.iloc[:,0:2]这种方法只能筛选出连续的列,那如果想要筛选的列分别在1,3,5,10:17怎么办呢?可以搜一下np.r_的用法。

  • 访问某一行列的元素

FY4aSf.png

删除、增加元素

  • 删除元素

我们使用.drop函数删除元素,默认为删除行,添加参数axis = 1来删除列。

FY5x2V.png

值得注意的是,drop函数不会修改原数据,如果想直接对原数据进行修改的话,可以选择添加参数inplace = True或用原变量名重新赋值替换。

  • 增加元素

这里介绍了两种方法,一种是append(),另外一种是insert(),这两种方法都比较简单,可类比于python list中的两种方法进行学习。

此外,Pandas还提供了其他更为复杂的做DataFrame融合的函数,比如说concat()merge()join()等等,相对难理解一些,我会单独出一份导学详细介绍这几个数据融合函数。

更改行列标签

使用函数rename()即可。具体用法如下:

FYIIiR.png

除此之外,还可以使用隐匿函数lambda来对行列标签进行统一处理,比如:

FYIoJ1.png

需要注意的是,rename()函数同样不会更改原数据,如果想直接对原数据进行修改的话,可以选择添加参数inplace = True或用原变量名重新赋值替换。

更改索引

可以使用函数set_index(index_label),将数据集的index设置为index_label

除此之外,还可以使用函数reset_index()重置数据集的index为0开始计数的数列。

缺失值(NaN)处理

NaN就是Not a Number的缩写,表示这里有数据缺失。

  • 查找NaN

我们可以使用isnull()notnull()函数来查看数据集中是否存在缺失数据,在该函数后面添加sum()函数来对缺失数量进行统计。除此之外,还可以使用count()函数对非NaN数据进行统计计数。

FYIzFA.png

  • 删除NaN

使用dropna(axis)函数可以删除包含NaN的行或列。

dropna()函数还有一个参数是how,当how = all时,只会删除全部数据都为NaN的列或行。

同样,该函数也不会修改原数据集。

  • 替换NaN

使用fillna()函数可以替换NaN为某一值。其参数如下:

  1. value:用来替换NaN的值
  2. method:常用有两种,一种是ffill前向填充,一种是backfill后向填充
  3. axis:0为行,1为列
  4. inplace:是否替换原数据,默认为False
  5. limit:接受int类型的输入,可以限定替换前多少个NaN

一般来说,我们常用均值去替换NaN。

还可以使用interpolate()函数按照某一方法来替换NaN,课程中介绍了method为linear时的用法,即忽略索引并将值视为相等间距,这是该函数的默认方法。更多method及解读请戳pandas.DataFrame.interpolate

在Pandas中处理数据

1
2
3
4
5
6
7
8
9
df = pd.read_csv(filename) #读取csv文件
df.info() #查看数据集信息
df.head() #查看前五行
df.tail() #查看后五行
df.sample() #查看随机一行
df.describe() #查看数据类型的基本统计信息
df.corr() #查看各列之间的相关系数

df.groupby() #将数据按照某一列进行聚类,后续接数据统计函数,如mean(),sum()等

项目内容

本首是Python项目的第2周,主要还是理解项目和准备项目文件,请大家做到以下几点:

Project2/week1的项目要求:(应该已经做完)

Project2/week2的项目要求 (本周要求和,做完了画第二个勾勾)

  • 用spyder打开项目文件浏览
  • 了解项目文件中有几个函数,函数名和输入是什么(不用看明白和尝试做)

SQL应知应会

作者 Allen Jia
2018年10月6日 21:30

If you want your life to be a magnificent story, then begin by realising that you are the author.

本文就Udacity数据分析入门课程中的SQL入门(P1阶段)和SQL进阶(P3阶段)的知识点进行总结。SQL的主要功能不外乎增、删、改、查四个,对于数据分析师来说,只需要掌握就可以了。(因为增删改往往超出了数据分析师的职能范围)

注意:本文是总结性质的,只能提供复习或者速查的功能,讲解得不会很详细,若想学习,还是要在教室内逐章学习。

SQL简介

SQL是Structured Query Language的简写,也就是结构化查询语言。SQL 最受欢迎的功能是与数据库交互。

使用传统关系数据库与 SQL 交互有一些主要优点。最明显的 5 个优点是:

  • SQL 很容易理解。
  • 传统的数据库允许我们直接访问数据。
  • 传统的数据库可使我们审核和复制数据。
  • SQL 是一个可一次分析多个表的很好工具。
  • 相对于 Google Analytics 等仪表板工具,SQL 可使我们分析更复杂的问题。

为什么企业喜欢使用数据库

  1. 只有输入了需要输入的数据,以及只有某些用户能够将数据输入数据库,才能保证数据的完整性
  2. 可以快速访问数据 - SQL 可使我们从数据库中快速获取结果。 可以优化代码,快速获取结果。
  3. 可以很容易共享数据 - 多个人可以访问存储在数据库中的数据,所有访问数据库的用户获得的数据都是一样。

SQL 与 NoSQL

你可能听说过 NoSQL,它表示 Not only SQL(不仅仅是 SQL)。使用 NoSQL 的数据库时,你编写的数据交互代码会与本节课所介绍的方式有所不同。NoSQL 更适用于基于网络数据的环境,而不太适用于我们现在要介绍的基于电子表格的数据分析。最常用的 NoSQL 语言之一是 MongoDB

SQL入门

SQL书写规则

  • SQL语句不区分大小写,因此SELECT与select甚至是SeLect的效果是相同的,但是要对命令和变量进行区分,所以默认命令需要大写,其他内容如变量等则需要小写
  • 表和变量名中不要出现空格,可使用下划线_替代。
  • 查询语句中,使用单一空格隔开命令和变量
  • 为提高代码的可移植性,请在查询语句结尾添加一个分号

SQL中的注释

  • 行内注释

    使用两个连字符-,添加注释。

    1
    2
    SELECT col_name -- 这是一条注释
    FROM table_name;
  • 多行注释

    多行注释以/*起始,以*/结尾。

    1
    2
    3
    4
    /*SELECT col_name 
    FROM table_name;*/
    SELECT col_2
    FROM table_name;

检索数据(SELECT FROM LIMIT )

检索数据主要用的语句为:SELECT

检索单列

1
2
SELECT col_name
FROM table_name;

从table_name表中检索col_name列。

检索多列

1
2
SELECT col_1,col_2,col_3
FROM table_name;

从table_name表中检索col_1,col_2和col_3列。

检索所有列

1
2
SELECT *
FROM table_name;

使用通配符*,返回table_name表中的所有列;

检索某列中不同的值

1
2
SELECT DISTINCT col_1
FROM table_name;

检索col_1中具有唯一性的行,即唯一值。

限制检索的结果

使用LIMIT语句可以限制返回的行数。

1
2
3
SELECT col_1
FROM table_name
LIMIT 10;

返回前10行(即第0-第9行)。

也可以添加OFFSET语句,设置返回数据的起始行:

1
2
3
SELECT col_1
FROM table_name
LIMIT 10 OFFSET 5;

从第五行之后,返回十行数据(即第5-第14行)。

排序检索数据(ORDER BY)

  • ORDER BY 语句用于根据指定的单列或多列对结果集进行排序。

  • ORDER BY 语句默认按照升序对记录进行排序。(从小到大,从a到z)

  • 如果您希望按照降序对记录进行排序,可以使用 DESC 关键字。

  • 在指定一条ORDER BY子句时,应该保证它是SELECT语句中的最后一条子句。

按列排序

1
2
3
SELECT col_name
FROM table_name
ORDER BY col_name;

返回的数据会按照col_name列进行升序排序,这里col_name可以是单列也可以是多列,当然也可以使用非检索的列进行排序。

降序排序

1
2
3
SELECT col_1,col_2
FROM table_name
ORDER BY col_2 DESC,col_3;

返回的数据会按照col_2列降序,col_3列升序对col_1和col_2两列进行排序。

这里可以看出,DESC关键字的用法:只对跟在语句前面的变量有效。所以,想要对多列进行降序排序时,需要对每一列都指定DESC关键字。

过滤数据(WHERE)

  • WHERE子句应该在表名(即FROM子句)之后给出。

  • WHERE子句应在ORDER BY子句之前。

  • 在过滤条件中的value是区分大小写的。

使用方法

1
2
3
SELECT col_1
FROM table_1
WHERE col_1 运算符 value;

运算符

运算符描述
=等于
<>不等于
>大于
<小于
>=大于等于
<=小于等于
BETWEEN…AND…在指定的两值之间
IS NULL为NULL值
AND逻辑运算符:与
OR逻辑运算符:或
IN制定条件范围筛选,可以简化OR的工作
NOT逻辑运算符:非

注意:

  • SQL的版本不同,可能导致某些运算符不同(如不等于可以用!=表示),具体要查阅数据库文档。
  • 在同时输入AND和OR时,SQL会优先处理AND语句,你可以使用小括号来进行分组操作。

用通配符进行过滤(LIKE)

通配符是用来匹配值的一部分的特殊字符,跟在LIKE关键字后面进行数据过滤

通配符描述
%表示任何字符出现任意次数
_表示任何字符出现一次
[]指定一个字符集,它必须匹配该位置的一个字符
^在[]中使用,表示否定

示例:

1
2
3
4
SELECT col_1
FROM table_1
WHERE col_1 LIKE '_[^JM]%'
ORDER BY col_1;

如上筛选出的是,第二个字符为非J或M的数据。

创建计算字段

其实就是在检索数据的同时进行计算,并使用关键字AS将结果保存为某一列。

  • 数值类型的计算
1
2
3
SELECT prod_id,quantity,item_price,quantity*item_price AS expanded_price
FROM orderitems
WHERE order_num = 200008;

输出:

1
2
3
4
prod_id         quantity       item_price      expanded_price
--------------------------------------------------------------
RGAN0154.990024.9500
BR03511.990059.9500

这里实现的就是使用quantity*item_price创建一个名为expanded_price的计算字段,也就是一个新列。

同样适用于计算的操作符有+(加),-(减)和/(除)。

  • 字符类型的拼接
1
2
3
SELECT RTRIM(col_name) + '('+RTRIM(col_country)+')' AS col_title
FROM table_name
ORDER BY col_name;

输出:

1
2
3
4
5
col_title
------------------------
Bear Emporium(USA)
Bears R Us(USA)
Jouets et ours(France)

这里实现的就是将col_name列与col_country列进行了拼接,新列的名字叫做col_title。

RTRIM()函数是去掉右边的所有空格,LTRIM()是去掉左边的所有空格,TRIM()是去掉两边的所有空格。

使用别名

在上一节中我们使用AS来为变量设置别名,你可能也见过如下所示的语句:

1
SELECT col1 + col2 AS total, col3

当然没有 AS 的语句也可以实现使用别名:

1
FROM tablename t1

以及

1
SELECT col1 + col2 total, col3

将col1+col2的结果设置名为total的列。

代码总结

语句使用方法其他详细信息
SELECTSELECT Col1, Col2, …提供你需要的列
FROMFROM Table提供列所在的表格
LIMITLIMIT 10限制返回的行数
ORDER BYORDER BY Col根据列命令表格。与 DESC 一起使用。
WHEREWHERE Col > 5用于过滤结果的一个条件语句
LIKEWHERE Col LIKE ‘%me%’仅提取出列文本中具有 ‘me’ 的行
INWHERE Col IN (‘Y’, ‘N’)仅过滤行对应的列为 ‘Y’ 或 ‘N’
NOTWHERE Col NOT IN (‘Y’, “N’)NOT 经常与 LIKEIN 一起使用。
ANDWHERE Col1 > 5 AND Col2 < 3过滤两个或多个条件必须为真的行
ORWHERE Col1 > 5 OR Col2 < 3过滤一个条件必须为真的行
BETWEENWHERE Col BETWEEN 3 AND 5一般情况下,语法比使用 AND 简单一些

SQL进阶

链接表

基本链接(JOIN)

SQL最强大的功能之一就是能在数据查询的执行中进行表的链接(JOIN)。

在关系数据库中,将数据分解为多个表能更有效地存储,更方便地处理,但这些数据储存在多个表中,怎样用一条SELECT语句就检索出数据呢?那就要使用链接。

创建链接的方式很简单,如下便是使用WHERE创建链接:

1
2
3
SELECT col_1,col_2,col_3
FROM table_1,table_2
WHERE table_1.id = table2.id;

如上,col_1和col_2属于table_1表中,col_3属于table_2表中,而这两个表使用相同的id列进行匹配。这种方法被称为等值链接,也就是内链接,我们可以使用如下的语句,更直观地实现内连接:

1
2
3
SELECT col_1,col_2,col_3
FROM table_1 INNER JOIN table_2
ON table_1.id = table2.id;

当然你也可以使用别名,简化输入,并且标明各列与表的隶属关系:

1
2
3
SELECT t1.col_1,t1.col_2,t2.col_3
FROM table_1 t1 INNER JOIN table_2 t2
ON t1.id = t2.id;

如上代码同样适用于左链接、右链接和外链接:

  • LEFT JOIN - 用于获取 FROM 中的表格中的所有行,即使它们不存在于 JOIN 语句中。

  • RIGHT JOIN - 用于获取 JOIN 中的表格中的所有行,即使它们不存在于 FROM 语句中。

  • FULL JOIN: 只要其中一个表中存在匹配,就返回行。

自链接

自链接经常用于对子查询的简化,如下示例:

假如要给Jim同一公司的所有顾客发送一封邮件,需要你先筛选出Jim的公司,然后再根据该公司筛选出所有的顾客。使用子查询的方式如下:

1
2
3
4
5
SELECT cust_id,cust_name,cust_contact
FROM customers
WHERE cust_name = (SELECT cust_name
FROM customers
WHERE cust_contact = 'Jim')

如果改为自链接的方式如下:

1
2
3
4
SELECT c1.cust_id,c1.cust_name,c1.cust_contact
FROM customers c1,customers c2
WHERE c1.cust_name = c2.cust_name
AND c2.cust_name = 'Jim';

结果是一样的,但是使用自链接的处理速度比子查询要快得多。

组合查询(UNION)

UNION 操作符用于合并两个或多个 SELECT 语句的结果集,使用方法也很简单,只要在多条SELECT语句中添加UNION关键字即可。

多数情况下,组合相同表的多个查询所完成的任务与具有多个WHERE子句的一个查询是一样的。

注意:UNION 内部的 SELECT 语句必须拥有相同数量的列,列也必须拥有相似的数据类型。而且UNION返回的结果只会选取不同的值(即唯一值)。

使用UNION的场合情况:

  • 在一个查询中从不同的表返回结果;
  • 对一个表执行多个查询返回结果。

示例:如下三个语句的结果是一致的。

  • 原始语句
1
2
3
4
5
6
7
8
9
-- 查询一
SELECT cust_name,cust_email
FROM customers
WHERE cust_state IN ('str1','str2');

--查询二
SELECT cust_name,cust_email
FROM customers
WHERE cust_name = 'str3';
  • 使用UNION链接
1
2
3
4
5
6
7
8
SELECT cust_name,cust_email
FROM customers
WHERE cust_state IN ('str1','str2')
UNION
SELECT cust_name,cust_email
FROM customers
WHERE cust_name = 'str3'
ORDER BY cust_name;

在最后添加了ORDER BY对所有SELECT语句进行排序,这里只是为了示例在使用UNION时如何进行排序。

  • 使用WHERE
1
2
3
4
SELECT cust_name,cust_email
FROM customers
WHERE cust_state IN ('str1','str2')
OR cust_name = 'str3';

这里看起来使用UNION比WHERE更复杂,但对于较复杂的筛选条件,或者从多个表中检索数据时,使用UNION更简单一些。

  • UNION ALL 命令和 UNION 命令几乎是等效的,不过 UNION ALL 命令会列出所有的值。

SQL聚合

有时候我们只是需要获取数据的汇总信息,比如说行数啊、平均值啊这种,并不需要吧所有数据都检索出来,为此,SQL提供了专门的函数,这也是SQL最强大功能之一。

聚合函数

SQL的聚合函数如下所示:

函数说明
AVG()返回某列的均值
COUNT()返回某列的行数
MAX()返回某列的最大值
MIN()返回某列的最小值
SUM()返回某列的和

使用示例:

1
2
SELECT AVG(col_1) AS avg_col_1
FROM table_1;

注意:聚合函数都会忽略列中的NULL值,但是COUNT(*)也就是统计全部数据的行数时,不会忽略NULL值。

聚合不同值

当添加DISTINCT参数时,就可以只对不同值(也就是某列中的唯一值)进行函数操作。

使用示例:

1
2
SELECT AVG(DISTINCT col_1) AS avg_col_1
FROM table_1;

数据分组

创建分组(GROUP BY)

前面的函数操作都是基于整个表去进行的,那如果想要依据某列中的不同类别(比如说不同品牌 不同性别等等)进行分类统计时,就要用到数据分组,在SQL中数据分组是使用GROUP BY子句建立的。

在使用GROUP BY时需要注意的几点:

  • GROUP BY子句可以包含任意数量的列,因而可以对分组进行多重嵌套,类似于Pandas中的多重索引;
  • GROUP BY子句必须出现在WHERE子句之后,ORDER BY之前。

使用示例:

1
2
3
SELECT col_1,COUNT(*) AS num_col
FROM table_1
GROUP BY col_1;

以上即可实现按col_1列中的不同类目进行行数统计。

过滤分组(HAVING)

在SQL入门中我们学过WHERE,它是对行数据进行筛选过滤的,那么,如果我想对创建的分组数据进行筛选过滤呢?这时候,你就要用到HAVING子句了,它与WHERE的操作符一致,只是换了关键字而已。

使用示例:

1
2
3
4
SELECT col_1,COUNT(*) AS num_col
FROM table_1
GROUP BY col_1
HAVING COUNT(*) >= 2;

这里我们就筛选出了具有两个以上类别的分组。

注意:使用HAVING时应该结合GROUP BY子句。

时间序列的处理(DATE)

在SQL中有一套专门的内置函数,用来处理时间序列,那就是DATE函数。

SQL Date 数据类型

先了解一下在不同的数据库中的时间序列的表示。(了解即可)

MySQL 使用下列数据类型在数据库中存储日期或日期/时间值:

  • DATE - 格式:YYYY-MM-DD
  • DATETIME - 格式:YYYY-MM-DD HH:MM:SS
  • TIMESTAMP - 格式:YYYY-MM-DD HH:MM:SS
  • YEAR - 格式:YYYY 或 YY

SQL Server 使用下列数据类型在数据库中存储日期或日期/时间值:

  • DATE - 格式:YYYY-MM-DD
  • DATETIME - 格式:YYYY-MM-DD HH:MM:SS
  • SMALLDATETIME - 格式:YYYY-MM-DD HH:MM:SS
  • TIMESTAMP - 格式:唯一的数字

DATE_TRUNC函数

DATE_TRUNC 使你能够将日期截取到日期时间列的特定部分。常见的截取依据包括日期月份年份

语法:

1
DATE_TRUNC('datepart', timestamp)

其中datepart即为你的截取依据,后面的timestamp类型可以参考上面的Date数据类型。

我总结了一份SQL的datepart速查表放在了下面。

使用示例:

1
2
3
4
5
SELECT DATE_TRUNC('y',col_date) col_year
FROM table_1
GROUP BY 1
ORDER BY 1 DESC
LIMIT 10;

如上,我们将col_date列按照年(’y’)进行了分组,并按由大至小的顺序排序,取前10组数据。

DATE_PART函数

DATE_PART 可以用来获取日期的特定部分,如获取日期2018-10-6的月份,只会获得一个结果6,这是它与DATE_TRUNC的最大区别。

语法:

1
DATE_PART ('datepart', date或timestamp)

其中datepart即为你的截取依据,后面的timestamp类型可以参考上面的Date数据类型。

使用示例:

1
2
3
SELECT DATE_PART('y',col_date) col_year
FROM table_1
GROUP BY 1;

如上,我们筛选了col_date列的年份,并依据它做了分组。

想了解更多DATE函数,可以戳SQL日期和时间函数参考

datepart总结

如下给了很多的缩写,只记住最简单的即可。

日期部分或时间部分缩写
世纪c、cent、cents
十年dec、decs
y、yr、yrs
季度qtr、qtrs
mon、mons
w,与 DATE_TRUNC一起使用时将返回离时间戳最近的一个星期一的日期。
一周中的日 ( DATE_PART支持)dayofweek、dow、dw、weekday 返回 0–6 的整数(星期日是0,星期六是6)。
一年中的日 ( DATE_PART支持)dayofyear、doy、dy、yearday
d
小时h、hr、hrs
分钟m、min、mins
s、sec、secs
毫秒ms、msec、msecs、msecond、mseconds、millisec、millisecs、millisecon

CASE语句

CASE语句其实就相当于python中的if语句,是用来做条件的。

需要注意的几点:

  • CASE 语句始终位于 SELECT 条件中。
  • CASE 必须包含以下几个部分:WHEN、THEN 和 END。ELSE 是可选组成部分,用来包含不符合上述任一 CASE 条件的情况。
  • 你可以在 WHEN 和 THEN 之间使用任何条件运算符编写任何条件语句(例如 WHERE),包括使用 AND 和 OR 连接多个条件语句。
  • 你可以再次包含多个 WHEN 语句以及 ELSE 语句,以便处理任何未处理的条件。

使用示例:

1
2
3
4
SELECT account_id, CASE WHEN standard_qty = 0 OR standard_qty IS NULL THEN 0
ELSE standard_amt_usd/standard_qty END AS unit_price
FROM orders
LIMIT 10;

如上,我们使用CASE WHEN.(条件一).THEN.(条件一的结果).ELSE.(其他不符合条件一的结果).END语句,设立的两个条件,即当standard_qty为0或者不存在时我们返回0,当standard_qty不为0时进行计算,并储存为新列unit_price。

子查询与临时表格

我们之前所涉及到的都是从数据库表中检索数据的单条语句,但当我们想要检索的数据并不能直接从数据库表中获取,而是需要从筛选后的表格中再度去查询时,就要用到子查询和临时表格了。

子查询与临时表格所完成的任务是一致的,只不过子查询是通过嵌套查询完成,而另一种是通过WITH创建临时表格进行查询。

构建子查询

构建子查询十分简单,只需将被查询的语句放在小括号里,进行嵌套即可,但在使用时一定要注意格式要清晰。

使用示例:

1
2
3
4
5
6
7
SELECT *
FROM (SELECT DATE_TRUNC('day',occurred_at) AS day,channel, COUNT(*) AS events
FROM web_events
GROUP BY 1,2
ORDER BY 3 DESC) sub
GROUP BY channel
ORDER BY 2 DESC;

如上,我们创建了一个子查询,放在小括号里,并将其命名为sub。在子查询中也注意到了各个子句上下对齐,这样条例更清晰。

临时表格(WITH)

这种方法,就是使用WITH将子查询的部分创建为一个临时表格,然后再进行查询即可。

我们还是使用上面子查询的例子,这次用临时表格的形式实现:

1
2
3
4
5
6
7
8
9
10
WITH sub AS(
SELECT DATE_TRUNC('day',occurred_at) AS day,channel, COUNT(*) AS events
FROM web_events
GROUP BY 1,2
ORDER BY 3 DESC)

SELECT *
FROM sub
GROUP BY channel
ORDER BY 2 DESC;

如上,我们将被嵌套的子查询单独拎出来,用WITH创建了一个临时表格,再之后又使用SELECT根据该表格进行查询。

SQL数据清理

这一节主要针对数据清理讲解了几个SQL中的常用函数,一般来说,也都是用在筛选阶段,更详尽的数据清理还是要放在python中去进行。

字符串函数

  • LEFT、RIGHT、LENGTH

LEFT和RIGHT相当于是字符串截取,LEFT 是从左侧起点开始,从特定列中的每行获取一定数量的字符,而RIGHT是从右侧。

LENGTH就是获取字符串的长度,相当于python中的len()。

语法:

1
2
3
LEFT(phone_number, 3) -- 返回从左侧数,前三个字符
RIGHT(phone_number, 8)
LENGTH(phone_number)
  • POSITIONSTRPOSSUBSTR

这三个函数都是与位置相关的函数。

POSITIONSTRPOS 可以获取某一字符在字符串中的位置,这个位置是从左开始计数,最左侧第一个字符位置为1,但他俩的语法稍有不同。

SUBSTR可以筛选出指定位置后指定数量的字符。

语法:

1
2
3
POSITION(',' IN city_state)
STRPOS(city_state, ‘,’) --跟上面的语句等价
SUBSTR(city_state,4,5) -- 返回city_state字符串中,以第4个字符开头的5个字符。
  • 字符串拼接(CONCAT)

顾名思义,就是将两个字符串进行拼接。

语法:

1
2
3
CONCAT(first_name, ' ', last_name) -- 结果为:first_name last_name
--或者你也可以使用双竖线来实现上述任务
first_name || ' ' || last_name

更改数据格式

  • TO_DATE函数

TO_DATE函数可以将某列转为DATE格式,主要是将单独的月份或者年份等等转换为SQL可以读懂的DATE类型数据。

语法:

1
2
TO_DATE(col_name,'datepart') 
TO_DATE('02 Oct 2001', 'DD Mon YYYY');

这里是将col_name这列按照datepart转化为DATE类型的数据,datepart可以参考之前的总结。

  • CAST函数

CAST函数是SQL中进行数据类型转换的函数,但经常用于将字符串类型转换为时间类型。

语法:

1
2
3
CAST(date_column AS DATE)
-- 你也可以写成这样
date_column::DATE

这里是将date_column转换为DATE格式的数据,其他时间相关的数据类型与样式对照可以参考上面写过的SQL Date数据类型,确保你想转换的数据样式与数据类型对应。

缺失值的处理

之前有提到过如何筛选出缺失值,即使用WHERE加上IS NULL或者IS NOT NULL

那么如何对缺失值进行处理呢?(其实这里可以直接无视,筛选出来后在python中再进行处理)

SQL中提供了一个替换NULL值的函数COALESCE

使用示例:

1
2
COALESCE(col_1,0) -- 将col_1中的NULL值替换为0
COALESCE(col_2,'no DATA') -- 将col_2中的NULL值替换为no DATA

总结

好啦,至此课程中的所有SQL知识点已经总结完了,并且也给大家做了适当的补充,希望大家能够用得上。未来的数据分析师之路,还要继续加油呀!

附:SELECT子句顺序

下表中列出了全文中涉及到的子句,在进行使用时,应严格遵循下表中从上至下的顺序。

子句说明是否必须使用
SELECT要返回的列或表达式
FROM用于检索数据的表仅在从表中选择数据时使用
JOIN…ON…用于链接表仅在需要链接表时使用
WHERE过滤行数据
GROUP BY分组数据仅在按组计算时使用
HAVING过滤分组
ORDER BY对输出进行排序
LIMIT限制输出的行数
❌
❌