方法的定义

  • Java的方法类似于其它语言的函数,是一段用来完成特定功能代码片段

  • 方法包含一个方法头和一个方法体

  •   修饰符 返回值类型 方法名(参数类型 参数名) {
          方法体
              
          return 返回值;
      }
      
    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
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76

    - 方法**可以不包含任何参数**

    ---

    ## 值传递和引用传递

    ### 值传递 pass by value

    在调用函数时,**将实际参数复制一份**传递到函数中,这样在行数中对参数进行修改,就**不会影响到原来的实际参数**。

    ### 引用传递 pass by reference

    在调用函数时,**将实际参数的地址**直接传递到函数中,这样在函数中对参数进行的修改就**会影响到实际参数**。

    ---

    ### Java是值传递

    - Java中,调用函数时,如果参数是**对象**,**实际参数**其实就是**指向对象的地址**。

    如果传递的是地址,就**看这个地址的变化**,而**不是看地址指向的对象的变化**。

    - 值传递和引用传递的**区别并不是传递的内容**,而是**实际参数是否被复制**并传递到函数。

    ---

    ## 方法的重载

    - 重载就是在一个类中,有**相同的函数名称**,但**参数不同**的函数。

    - 方法的重载规则

    - **方法名称**必须**相同**
    - **参数列表**必须**不同**(**个数**不同/**类型**不同/参数**排列顺序**不同等)
    - 方法的**返回类型不限制**(可以相同也可以不同)
    - 仅仅返回类型不同不足以成为方法的重载

    - 实现理论

    方法名称相同时,编译器会根据调用方法的参数个数、类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。

    ---

    ## 可变参数

    - JDK 1.5开始,Java支持传递**同类型的可变参数**给一个方法

    - 在方法声明中,在指定参数类型后加一个省略号`...`

    - **一个方法中只能指定一个可变参数**,它**必须是方法的最后一个参数**

    - ```java
    public class Demo {
    public static void main(String[] args) {
    printMax(34, 4, 6, 78, 232.4);
    printMax(new double[]{1, 2, 3})
    }

    public static void printMax(double... numbers) { // 可变参数
    if (numbers.length == 0) {
    System.out.println("No argument passed");
    return;
    }

    double result = numbers[0];

    // 获取最大值
    for (int i = 0; i < numbers.length; i++) {
    if (numbers[i] > result) {
    result = numbers[i];
    }
    System.out.println("The max value is " + result);
    }
    }
    }

用户交互Scanner

java.util.ScannerJava5的特性。可以通过Scanner类获取用户的输入

  • 通过Scanner类的next()nextLine()方法获取输入的字符串,在读取前我们一般需要使用hasNext()hasNextLine()判断是否还有输入的数据

    • next()

      • 一定要读取到有效字符后才会结束输入;
      • 对输入有效字符前遇到的空白next()会将其自动去掉
      • 只有输入有效字符后才将其后面输入的空白作为分隔符或结束符
      • ==next()不能得到带有空格的字符串==
    • nextLine()

      • Enter为结束符
      • 可以获得空白
1
2
3
4
5
6
7
8
Scanner s = new Scanner(System.in);
// 判断用户有没有输入字符串
if (scanner.hasNext()) {
String str = scanner.next();
System.out.println("输出的内容为:" + str);
}
// 凡是属于I/O流的类,如果不关闭会一直占用资源。用完就关掉。
scanner.close();
  • 凡是属于I/O流的类,如果不关闭会一直占用资源。

    用完就关掉scanner.close()


Switch选择结构

  • 除了if else语句外,多选择结构还可以通过switch case语句实现
  • switch case语句判断一个变量一系列值中的某一个值是否相等,每个值称为一个分支
  • switch 语句中的变量类型可以是
    • byte、short、int、char
    • JDK 7开始,switch支持String类型
    • case标签必须是字符串常量字面量
1
2
3
4
5
6
7
8
9
10
11
switch (expression) {
case value:
// 语句
break; // 可选,但最好写上
case value:
// 语句
break; // 可选,但最好写上
// 可以有任意数量的case语句
default: // 可选,放最后
// 语句
}
  • ==case穿透==

    如果分支中没有break,当变量成功匹配上一个分支后,之后的分支会顺序执行,直到语句结束或遇到break。

    因此,规范写法:每个分支必须要有break

  • default

    变量值没能与其他分支匹配时,会执行default分支

    因此理论上应该放在最后


循环

while循环


do while循环

1
2
3
do {
// 代码语句
} while (布尔表达时);
  • while 和 do while循环的区别

    while先判断后执行,do while 先执行后判断

  • 记住while之后要加**分号;**,当然,好的IDE都会相关有纠错提示


for循环


  • for循环使一些循环结构变得更加简单;

  • for循环语句是支持迭代的一种通用结构,是最有效、最灵活的循环结构;

  •   for (初始化; 布尔表达式; 迭代) {
          // 代码语句
      }
      
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    ---

    ## 增强for循环 foreach

    - **Java5** 引入了主要用于**数组或集合**的增强型for循环;

    - ```java
    for (声明语句 : 表达式) {
    // 代码语句
    };
  • 声明语句

    • 声明新的局部变量,该变量的类型必须和数组元素的类型匹配
    • 作用域限定在循环语句块,其值与此时数组元素的值相等;
  • 表达式

    表达式时要访问的数组名,或者是返回值为数组的方法;

1
2
3
4
5
int[] numbers = {10, 20, 30, 40};

for (int x : numbers) {
System.out.println(x);
}

break、continue、goto

break

break用于强行退出循环,不执行剩余语句(break语句也在switch语句中使用)

continue

continue语句用在循环语句体中,用于终止某次循环过程。即跳过循环体中尚未执行的语句,直接进行下一次是否执行循环的判定

goto

goto关键字很早就在程序设计语言中出现。尽管goto仍然是Java的一个保留字,但并未得到正式使用;Java没有goto

但在break和continue身上,我们能看出一些goto的影子——带标签的break和continue。

  • 标签是指后面跟一个冒号的标识符,如label:

  • 对Java来说,唯一用到标签的地方是在循环语句之前

    设置标签的唯一理由:我们希望在其中嵌套另一个循环,由于break和continue关键字通常只中断当前循环,但若随同标签使用,它们就会中断到存在标签的地方

1
2
3
4
5
6
7
8
outer: for (int i = 0; i< 9; i++) {
forint j = 2; j < i / 2; j++) {
if (i % j == 0) {
continue outer;
}
}
System.out.print(i+" ");
}

  1. 快速输入public static void main(String[] args){}

    psvm + tab即可

  2. 快速输入System.out.println();

    • sout + tab即可
    • 可以先输入任何想要输出的变量,再.sout
  3. 创建对象或使用一个能返回对象的方法时,直接写new ClassName()/ClassName.methodName(parameters),再Alt + Enter,即可自动产生对象名。

  4. 谷歌IDEA优化,进行相关操作

  5. 运行Java文件

    • 上次运行过的文件:Shift + F10
    • 当前文件:Ctrl + Shift + F10
  6. 反编译

    1. 工具栏 - Project Structure… - Project - Project compiler output,找到编译成字节码的class文件输出地址,在文件资源管理器中打开该地址;
    2. IDEA中,在中意的Package右键 - Show in Explore
    3. 在文件资源管理器中,复制class文件到java文件所在的目录,之后再在IDEA中打开,即可查看反编译后的内容
  7. 编写构造器

    Alt + insert –> Constructor

  8. 编写Getter和Setter方法

    Alt + insert –> Getter and Setter

  9. 方法的重载

    Alt + insert –> Override Methods…

    1
    2
    3
    4
    5
    // 创建重载方法后,IDEA 会自动写一个注解(有功能的注释)
    @Override
    public void overrideMethod() {
    // 方法体
    }
  10. 包裹选中的代码

    Ctrl + Alt + T

  11. 自动清除无效import

    Settings -> Editor -> General -> Auto Import -> Java

    勾选Optimize imports on the fly

    image-20220818002554092

  12. 在底部的Git标签页中,添加Local Changes标签页

    效果:

    image-20221214231836532

    操作:File - Settings - Version Control - Commit - 取消勾选Use non-modal commit interfce

    image-20221214231903047
  13. 去掉import自动合并

IDEA java文件import去掉自动合并

  1. idea+tomcat实现热部署-修改java代码及时生效

日志

设置快捷生成日志的提示:

  1. File - Settings - Editor - Live Templates - 加号 - Live Template

image-20230917005706530

  1. Abbreviation填写期望的快捷缩写,Template text填写期望的填充效果

    private static final Logger LOGGER = LoggerFactory.getLogger($CLASS_NAME$.class);

  2. Edit Variables… - Expression选择className()

  3. 左下角Define,设置生效的场景:java: declaration

    image-20230917012446212

创建类时自动添加注释

  • File - Settings - Editor - File and Code Tmplates
  • Scheme: Default
  • Includes - File Header
image-20231009100131089

绪论

机器学习研究的主要内容,是关于在计算机上从数据中产生模型(model)的算法,即学习算法(learning algorithm)

《机器学习》一书中,模型泛指从数据中学得的结果,有时将模型称为学习器learner,可以看作学习算法在给定数据和参数空间上的实例化。有些文献,模型全局性结果,如一棵决策树模式局部性结果,如一条规则


基本术语

  • 数据集 data set:记录的集合

    • 示例/样本 instance/sample:关于一个事件或对象的描述。
  • 属性/特征 attribute/feature:反映事件或对象在某方面的表现或性质的事项。

  • 样本空间 sample space属性张成的空间。

    e.g. 把色泽、根蒂、敲声作为三个坐标轴,则张成一个用于描述西瓜的三维空间,每个西瓜都可以找到自己的坐标位置。

  • 一个样本也可以称为样本空间中的一个特征向量 feature vector

$D={x_1,x_2,…,x_m}$表示包含$m$个示例的数据集,每个示例包含$d$个属性描述。每个示例$x_i=(x_{i1};x_{i2};…;x_{id})$是d维样本空间中的一个向量。

  • 学习/训练 learning/training:从数据中学得模型的过程,这个过程通过执行某个学习算法来完成,学习过程就是为了找出或逼近真相

    • 训练数据 training data:训练过程中使用的数据

    • 训练样本 training sample

    • 训练集 training set:训练样本组成的集合

    • 标记 label:训练样本的结果信息

      拥有了标记label 的示例sample,称为样例example,一般用$(x_u,y_i)$表示第i个样例,$y_i$是示例$x_i$的标记。

    • $Y$是所有标记label的集合,称为标记空间label space/输出空间

  • 假设 hypothesis

    学得的模型对应了关于数据的某种潜在的规律

  • 真实 ground-truth客观规律自身

  • 预测模型 prediction model

  • 分类 classification

    预测离散值的学习任务。例如好瓜、坏瓜。

    • binary classification 二分类
      • positive class 正类
      • negative class 反类
    • multi-class classification 多分类

    一般地,预测prediction任务希望通过对训练集training set进行学习建立一个从输入空间X到输出空间Y的映射

  • 回归 regression

    预测连续值的学习任务。例如西瓜成熟度0.95、0.37。

  • 测试 testing

    testing sample 被预测的样本

  • 聚类 clustering

    将训练集中的西瓜分成若干组,每组称为一个簇cluster

    这些自动形成的簇可能对应一些潜在的概念划分,例如“浅色瓜”、“深色瓜”。

  • 监督学习 supervised learning

    训练数据拥有标记信息

    • 分类、回归
  • 无监督学习 unsupervised learning

    训练数据没有标记信息

    • 聚类
  • 泛化 generalization

    学得模型适用于新样本的能力,称为泛化能力。

通常假设样本空间中全体样本服从一个未知分布(distribution)D,我们获得的每个样本都是独立地从这个分布上采样获得的,即“独立同分布”(independent and identically distributed)


假设空间

  • 归纳 induction

    从特殊到一般泛化(generalization)过程,即从具体的事实归结出一般性规律

    从样例中学习显然是一个归纳的过程,因此亦称归纳学习(induction learning)

  • 演绎 deduction

    从一般到特殊特化(specialization)过程,即从基础原理推演出具体状况

可以把学习过程看作一个在所有假设(hypothesis)组成的空间中进行搜索的过程,搜索目标是找到与训练集**匹配(fit)**的假设。假设的表示一旦确定,假设空间及其规模大小就确定了。

现实问题中我们常面临很大的假设空间,但学习的过程是基于有限样本训练集进行的,因此可能有多个假设与训练集一致与训练集一致的假设集合,称之为**版本空间(version space)**。


归纳偏好

对于一个具体的学习算法而言,它必须要产生一个模型。学习算法本身的偏好会起到关键作用。

机器学习算法在学习过程中对某类假设的偏好,称为**归纳偏好(inductive bias)**。

是否存在一般性的原则来引导算法确立“正确”的偏好?

**“奥卡姆剃刀”(Occam’s razor)**原则

奥卡姆剃刀原则是一种常用的、自然科学研究中最基本的原则:若有多个假设与观察一致,则选最简单的那个

然而,奥卡姆剃刀并非唯一可行的原则,且其本身存在不同的诠释。例如:

  1. 好瓜↔(色泽 = *) ^ (根蒂 = 蜷缩) ^ (敲声 = 浊响)
  2. 好瓜↔(色泽 = *) ^ (根蒂 = 蜷缩) ^ (敲声 = *)

这两个假设何者更简单的问题并不简单,需要借助其他机制才能解决。


数据挖掘与机器学习的联系

数据库领域的研究为数据挖掘提供数据管理技术

机器学习和统计学的研究为数据挖掘提供数据分析技术;(统计学主要通过机器学习对数据挖掘发挥影响)


模型评估与选择

经验误差与过拟合

m个样本,a个样本分类错误

  • 错误率(error rate) E = a / m:分类错误的样本数占样本总数的比例
  • **精度(accuracy)**:1 - a / m

把训练样本自身的一些特点当作了所有潜在样本都会具有的一般性质,会导致泛化性能下降。这种现象在机器学习中称为**“过拟合”(overfitting)“欠拟合”(underfitting)**指对训练样本的一般性质尚未学好。


评估方法

包含$m$个样例的数据集$D={(x_1,y_1), (x_2,y_2),…,(x_m,y_m)}$,如果既要训练,又要测试,需要把数据集$D$进行适当的处理,从中产生出**训练集$S$测试集$T$**。对数据集$D$的处理方法,有以下常见的几种。


留出法 (hold-out)

留出法直接将数据集$D$划分为两个互斥的集合。在$S$上训练出模型后,用$T$来评估其测试误差,作为对泛化误差的估计

训练/测试集的划分要尽可能保持数据分布的一致性,避免因数据划分过程引入额外的偏差而对最终结果产生影响,例如在分类任务中至少要保持样本的类别比例相似。如果从采样的角度来看待数据集的划分过程,则保留类别比例的采样方式通常称为**分层采样(stratified sampling)**。若$S$、$T$中样本类别比例差别很大,则误差估计将由于训练/测试数据分布的差异而产生偏差。

即使给定训练/测试集的样本比例后,仍然存在多种划分方式对初始数据集$D$进行分割。因此,单次使用留出法得到的估计结果往往不够稳定可靠。使用留出法,一般要采用若干次随机划分、重复进行实验评估后取平均值作为留出法的评估结果。

常见的做法是将大约${\frac{2}{3}}$~${\frac{4}{5}}$的样本用于训练,剩余样本用于测试。


交叉验证法 (cross validation)

交叉验证法先将数据集$D$划分为$k$个大小相似的互斥子集,每个子集$D_i$都尽可能保持数据的一致性,即从$D$中通过分层采样得到。然后,每次用$k-1$个子集的并集作为训练集,剩下的那个子集作为测试集,这样就可以获得$k$组训练/测试集,最终返回$k$个测试结果的平均值

交叉验证法评估结果的稳定性和保证性很大程度上取决于k的取值,因此交叉验证法通常又称为**$k$折交叉验证($k$-fold cross validation)。$k$最常用的取值是10,此时称为10折交叉验证**,其他常用的k值有5、20等。

与留出法相似,将数据集$D$划分为$k$个子集同样存在多种划分方式。为减小因样本划分不同而引入的差别,$k$折交叉验证通常要随机使用不同的划分重复$p$次,最终结果是这$p$次$k$折交叉验证结果的均值,常见的有10次10折交叉验证


自助法 (bootstrapping)

自助法将给定包含$m$个样本的数据集$D$进行采样,产生数据集$D^{‘}$:每次随机从D中挑选一个样本,将其拷贝放入$D^{‘}$,这个过程重复执行$m$次,就得到了数据集$D^{‘}$。

$D$中有一部分样本会多次出现,另一部分样本则不出现。样本在$m$次采样中始终不被采到的概率是$(1-\frac{1}{m})^m$,取极限:$\displaystyle\lim_{x\to\infty}(1-\frac{1}{m})^m={\frac{1}{e}}\approx 0.368$。即通过自助采样,初始数据$D$中约有36.8%的样本不会出现在采样数据集$D^{‘}$中。于是,可将$D^{‘}$用作训练集,剩余样本用作测试集。这样,实际评估的模型与期望评估的模型都使用$m$个训练样本,而仍有数据总量约1/3的、没在训练集中出现的样本用于测试。这样的测试结果,亦称**“包外估计”(out-of-bag estimate)**。

自助法在数据集较小、难以有效划分训练/测试集时很有用;自助集能从初始数据集中产生多个不同的训练集,这对集成学习等方法有很大好处

然而,自助法产生的数据改变了初始数据集的分布,会引入估计偏差。因此,在初始数据量足够时,留出法和交叉验证法更常用


调参(parameter tuning)与最终模型

大多数学习算法都有些参数需要设定,参数配置不同,学得模型的性能往往有显著差别。因此,在进行模型评估与选择时,还需要算法参数进行设定,这就是常说的调参

学习算法的很多参数在实数范围内取值,因此对每种参数配置都训练出模型是不可行的。常用的做法是,对每个参数选定一个范围和变化步长。例如,在$[0, 0.2]$范围内以0.05为步长,则实际要评估的候选参数数值有5个,最终从这5个候选值中产生选定值。显然,这样选定的参数值往往不是最佳值,但这是计算开销和性能估计之间进行折中的结果,通过这种折中,学习过程才变得可行

在研究对比不同算法的泛化性能时,我们用测试集上的判别效果来估计模型在实际使用时的泛化能力。而把训练数据另外划分为训练集和验证集(validation set),基于验证集上的性能来进行模型选择和调参


性能度量 (performance measure)

对学习器的泛性能进行评估,不仅需要有效可行的实验估计方法,还需要有衡量模型泛化能力的评价标准,这就是性能度量

回归任务最常用的性能度量是均方误差(mean squared error)

$$E(f; D)=\frac{1}{m}\sum_{i=1}^{m}(f(x_i)-y_i)^2$$

更一般地,对于数据分布D和概率密度函数p(·),均方误差可描述为:

$$E(f; D)=\int_{x\sim D}(f(x)-y)^2p(x)dx$$


错误率与精度

经验误差与过拟合中,提到了错误率和精度。这是分类任务中最常用的两种性能度量,既适用于二分类任务,也适用于多分类任务。

  • 错误率

    $$E(f;D)=\frac{1}{m}\sum_{i=1}^mⅡ(f(x_i)\ne y_i)$$

  • 精度

    $$acc(f;D)=\frac{1}{m}\sum_{i=1}^mⅡ(f(x_i)=y_i)=1-E(f;D)$$

更一般地,对于数据分布D和概率密度函数p(·),错误率和精度可分别描述为

  • $$E(f;D)=\int_{x\sim D}Ⅱ(f(x)\ne y)p(x)dx$$
  • $$acc(f;D)=\int_{x\sim D}Ⅱ(f(x)=y)p(x)dx=1-E(f;D)$$

查准率(Precision)、查全率(Recall)与F1

对于二分类问题,可将样例根据其真实类别与学习器预测类别的组合划分为**真正例(True Positive)、假正例(False Positive)、真反例(True Negative)、假反例(False Negative)**。

  • 样例总数=$TP+FP+TN+FN$

  • 查准率P

    $$P=\frac{TP}{TP+FP}$$

  • 查全率R

    $$R=\frac{TP}{TP+FN}$$

  • 基于P和R的**调和平均(harmonic mean)**定义的F1

    $$\frac{1}{F1}=\frac{1}{2}(\frac{1}{P}+\frac{1}{R})$$

在一些应用中,对查准率和查全率的重视程度不同。例如,在商品推荐系统中,为了尽可能少打扰用户,更希望推荐内容确实是用户感兴趣的,此时查准率更重要;而在逃犯信息检索系统中, 更希望尽可能少漏掉逃犯,查全率更重要。F1度量的一般形式$F_\beta$,能表达出不同的偏好:

$$\frac{1}{F_\beta}=\frac{1}{1+\beta^2}(\frac{1}{P}+\frac{\beta^2}{R})$$


ROC与AUC

分类过程相当于以某个**截断点(cut point)**将样本分为两部分,前一部分判作正例,后一部分判作反例。

  • ROC(Receiver Operating Characteristic)

    受试者工作特征曲线,根据学习器的预测结果对样例进行排序,按此顺序逐个把样本作为正例进行预测,每次计算出两个重要量的值,分别作为横纵坐标作图,就得到了ROC曲线。

注释

1
2
3
4
5
6
7
8
9
10
11
// 单行注释

/*
多行注释
*/

/**
* JavaDoc:文档注释
*
*
*/

标识符

类名变量名方法名都被称为标识符

  • 关键字不能作为标识符
  • 所有的标识符都必须以字母、$或下划线_开始;首字符之后还可以选择数字作为字符。
  • 标识符中不能使用#*-等字符
  • 标识符是大小写敏感

数据类型

  • 强类型语言

    要求变量的使用严格符合规定,变量都必须先定义再使用。Java就是强类型语言。

  • 弱类型语言

    JavaScript就是弱类型语言。


Java的数据类型分为两大类

基本类型(primitive type, 8个)

  • 数值类型

    • 整数类型

      整数类型 大小 表示范围
      byte 8位 -128($-2^7$) ~ 127($2^7-1$)
      short 16位 $(-2^{15})$~$(2^{15}-1)$
      int 32位 $(-2^{31})$~$(2^{31}-1)$
      long 64位 $(-2^{63})$~$(2^{63}-1)$
      • long类型的变量,赋值时需要在最后加一个L,便于和int类型区分。(不加L也能通过编译,但是可读性差小写的l辨识度低,可读性也差)
      • float类型的变量,赋值时必须在最后加一个F
    • 浮点数类型

      浮点类型 大小
      float 32位
      double 64位

      最好完全不要使用浮点数进行比较,因为浮点数是有限位的、离散的、有舍入误差的数据类型。

      那么银行类业务应该用什么数据类型表示钱?——数学工具类 BigDecimal

    • 字符类型

      char,16位,赋值使用单引号''字符串String不是关键字,而是一个类,赋值使用**双引号""**。

    1
    2
    3
    4
    long num1 = 30L; 
    float num2 = 3.14F;
    char c = 'A';
    String str = "AS";

    JDK7的特性:赋值时,可以用下划线_分割过长的数字(整数&浮点数均可),提高可读性

  • 布尔类型 boolean

    1位,值为true/false,由所占空间大小可知,**实际是使用(1/0)**来表示。


引用类型(reference type)

    • String

      • 判断字符串是否相等

        str1.equals(str2)重写了继承自Object的方法

        而不是通过==来判断,因为String是引用类型中的==作用于对象实际比较的是地址

      •   String str = "test" // 实际等价于 String str = new String("test");
          
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
          	
        **不用创建对象的写法可以看作Java为了方便的设计,并不意味着String类是和char一样的基本类型**。

        - 接口

        - 数组

        ---

        ### 数据类型扩展

        #### 不同进制整数的特殊表示方法

        - **二**进制:**0b**开头
        - **八**进制:**0**开头
        - **十六**进制:**0x**开头

        ```java
        int num1 = 0b101; // 0b开头,2进制
        int i = 10;
        int i2 = 010; // 0开头,8进制
        int i3 = 0x10F; // 0x开头,16进制

字符转换成数字后的输出

1
2
3
4
5
6
char c1 = 'a';
char c2 = '\u0061' // \u为转义字符 Unicode,后跟4位16进制的数字,有对应的字符

System.out.println(c1);
system.out.println((int)c1); // 强制类型转换
System.out。println(c2);

类型转换

由于Java是强类型语言,所以进行某些运算的时候,需要用到类型转换。


基本数据类型的优先级

1
2
// 低 --------------------------------------------> 高
byte, short, char, int, long, float, double
  • 虽然float是32位表示,long是64位表示,但浮点数优先级就是比整数高
  • 布尔类型不能进行强制转换
  • 对象类型不能转换为不相干的类型
  • 类型转换可能存在内存溢出或者精度问题

字符串连接符

String类型的操作数**+另外类型的操作数,会将其他类型的操作数都转换成Stirng类型**后再连接。

1
2
System.out.println("" + 2 + 3); // 输出23
System.out.println(2 + 3 + ""); // 输出5

变量、常量、作用域

变量 Variable

1
2
// 数据类型 变量名 = 值
type varName [=value] [{, varName[=value]}];
  • 可以使用逗号隔开来声明多个同类型的变量,但是不建议这样操作(后续查找、修改的操作会变得麻烦,可读性差)。

  • 变量的默认初始化

    • 基本数据类型:

      • 整数类型:0
      • 浮点数类型:0.0
      • 字符类型:’\u0000’
      • 布尔类型:false
    • 引用数据类型:

      默认初始化为空值null,如String。


作用域

  • 类变量:从属于类,使用关键字static修饰
  • 实例变量:从属于对象,需要创建出对象后,才能使用
  • 局部变量:位于方法中

e.g. 见Demo1.java文件


常量 Constant

  • 初始化后不能更改,使用关键字final修饰;

修饰符不存在先后顺序

e.g. 以下两种写法等效

1
2
public static final double PI = 3.14;
static public final double PI = 3.14;

变量和常量的命名规范

  • 变量

    • 首字母小写驼峰原则:monthSalary
    • 方法名的规范同变量名
    • 类名要求首字母大写和驼峰原则
  • 常量

    常量一般用大写字母和下划线命名,便于和变量区分,提高可读性;


Math类

很多运算,我们会使用一些工具类来操作。

  • pow(double a, double b)

    返回一个结果为$a^b$的double值


运算符

位运算符

位运算与普通的四则运算相比执行效率高很多,因为位运算是直接以计算机底层的运行机制为基础操作的。

  • &

  • |

  • ~ 取反

  • 移位运算符

    • << 左移

      每移1位,等效于*2

    • >> 右移

      每移1位,等效于/2


三元运算符x?y:z

如果x == true,结果为y,否则结果为z


包机制

为了更好地组织类,Java提供了包机制,用于区别类名的命名空间包的本质 == 文件夹

  • 一般利用公司域名倒置作为包名

    com.hunter

  • 包语句的语法格式

    1
    package package1[.pkg2[.pkg3...]];

    必须是类文件中的首句

  • 为了能够使用某一个包的成员,需要在Java文件中使用import语句明确导入该包

    1
    import package1[.pkg2...].(classname|*); // *是通配符,导入指定包下所有的类
  • 尽量不要让不同包下的类名重复

    同一个类文件中,无法使用位于不同包下相同类名的类(会报错)


JavaDoc生成文档

  • javadoc命令是用来生成API文档

  • 文档注释

    1
    2
    3
    4
    /**
    *
    *
    */
    • 加在类上就是类注释
    • 加在方法上就是方法注释
  • 参数信息

    • @auther 作者名
    • @version 项目版本号
    • @since 指明需要最早使用的jdk版本
    • @param 参数名
    • @return 返回值情况
    • @throw 异常抛出情况

卸载JDK

  1. 删除Java的安装目录及其下所有文件
  2. 删除系统环境变量 - JAVA_HOME
  3. 删除系统环境变量Path下关于Java的目录
  4. 通过java -version命令,验证是否成功卸载(报错即成功卸载)

安装JDK

  1. 下载JDK 8
  2. 双击安装JDK
  3. 记住安装的路径

配置环境变量

  1. 我的电脑 - 右键 - 属性 - 高级系统设置 - 环境变量

  2. 系统变量 - 新建

    1. 变量名:JAVA_HOME
    2. 变量值:C:\Program Files\Java\jdk1.8.0_201
  3. 系统变量 - Path - 编辑 - 新建

    • 输入%JAVA_HOME%\bin%%的作用:引用路径
    • 输入%JAVA_HOME%\jre\bin

    确定,保存。

  4. 测试JDK是否安装成功

    打开命令行,输入java -version


Hello World 及 简单语法规则

  1. 创建一个java文件

    • 文件后缀名为.java
  2. 编写代码

    1
    2
    3
    4
    5
    public class Hello {
    public static void main(String[] args) {
    System.out.print("Hello, World!");
    }
    }
  3. 编译java文件

    javac xxx.java,会生成一个class文件

  4. 运行class文件

    java xxx(不写文件类型后缀)

注意事项

  1. Java是严格区分大小写
  2. 文件名类名必须保持一致,并且首字母大写

Java程序运行机制


使用IDEA

什么是IDE

IDE(Integrated Development Environment,集成开发环境)


IDEA 使用技巧

如何管理邮件

在**收件箱(inbox)**创建3个子文件夹:

  • Action 1.【行动】

    存放需要我做出执行的邮件。

    在其中将邮件按紧急程度归类。

    • 紧急(需当天执行
    • 不紧急
    • 他人响应后处理

    按类别排序,可以让你按不同的紧急程度来浏览处理邮件

  • Read【阅读】

    需要花时间去仔细阅读,但不需要我做出回复

  • Archive【存档】

    • 涉及基础需要的邮件
    • 不想删除的邮件

    通过会话的排序规则来整理这个文件夹,能对这些邮件沟通有一个宏观的回顾,同时隔离掉相同的邮件会话内容


创建类别

类别能将邮件、任务、日历、会议等功能都实现分组。(类别不只对应邮件

创建类别的方式:

  1. 导航栏 - 类别 - 管理类别

  2. 选中红色类别,重命名为紧急

  3. 选中橙色类别,重命名为不紧急

  4. 选中蓝色类别,重命名为他人响应后处理


创建和运用快速步骤

拖曳的方式移动邮件、邮件,过于低效累人。

利用快速步骤(Quick Steps),能够快速完成邮件的整理,从而易于思考和行动。

快速步骤

点击快速步骤面板右下角的小箭头,对快速步骤进行管理设置。

移动邮件

  1. 选择移动邮件(move to ?) - 编辑 - 命名为【存档】
  2. 选中移至文件夹指令,再选中已创建的【存档】文件夹
  3. 想把【存档】文件夹里的邮件标记为已读,所以选中已读标记
  4. 添加动作 - Clear Categories)
  5. 添加动作 - Clear flags on message
  6. 保存

删除其余用不到的快速步骤


创建新的快速步骤执行指令

  • 新建 - xxx - 命名为【行动 - 紧急
  • 换一个图标,如惊叹号
  • 选中移至文件夹指令,选中【行动】文件夹
  • 添加动作 - Flag Message - today
  • 添加动作 - categories message - 紧急
  • 完成

以此类推,【行动 - 不紧急】:

  • 把图标换为旗帜
  • 添加动作 - Flag Message - this week
  • 添加动作 - categories message - 不紧急

【行动 - 他人响应后处理】:

  • 把图标换为信息(一个i)
  • 添加动作 - Flag Message - no date
  • 添加动作 - categories message - 他人响应后处理

【阅读】

  • 把图标换为书本(一个i)
  • 不用添加动作

【行动】文件夹里各类别邮件的日程安排

当通过上述操作整理好邮件后,就能真正专注于处理【行动】文件夹中的邮件。

例如有些邮件是不紧急的,但接下来几个小时里我无法跟进,就需要安排进我的日程中,从而避免忘记或影响当前的工作

将邮件转为日历只需拖住该封邮件到日历选项即可。

  • 单击导航栏 - 计划(scheduling)
  • 单击 自动选取下一个(autopick next)按钮,让我能在我的日程中快速找到下一个空闲的时间段,保存并关闭。

定制outlook的启动文件夹视图

如果想始终将精力放在最重要的事情上,最好的方法就是定制outlook视图。就是先看到、处理【行动】文件夹里的内容。

文件 - 选项 - 高级 - outlook启动和退出 - 在此文件夹中启动outlook - 浏览 - 找到【行动】文件夹,确定即可。


处理邮件的4D决策模型

理想状态下,阅读邮件的时间安排:

  1. 早上 9:00 - 10:00 (1个小时)
  2. 下午1:30 - 2:00 (半个小时)
  3. 当天工作结束后,半个小时

大多数人好奇或习惯不停查看收件箱,但这一行为会让自己变得非常低效,因为无法专注在优先级的工作上。这意味着,每个时间段处理邮件需要快速做出决策

4D决策模型:

  1. Delete it 删除(归档)

  2. Do it 执行

    2分钟问题(2-minute drill),针对当前邮件我需要采取的行动在两分钟内完成吗?

    如果可以,就立即执行。如果能在两分钟内完成任务,就不需要关闭电子邮件或退出outlook

  3. Delegate it 委派

    2-minute drill答案为否时,能转发给一个合适的团队成员来处理吗?

    如果是,就花两分钟时间撰写要求、情况并转发出去。然后就可以将邮件归档以便追踪

  4. Defer it 推迟

    上述3D都无法完成,这意味着邮件中要求的工作只有自己才能完成,而且需要2分钟以上的时间来处理的事务 。那么就需要推迟它,在查阅处理完邮件再去执行它。进行推迟操作时,记得将这些讯息用相应的快速步骤标记类别到【行动】文件夹。

每天运用4D决策模型,就能很容易、快速地完成大量的邮件处理


创建邮件规则

处理灰色邮件(广告、垃圾邮件)

……


摘取自:借助Outlook最大化提升你的个人工作生产力①_腾讯视频

求三位数组合

已知有一个list,里面有4个数字,分别是3,6,2,7 ,这四个数字能组成多少个互不相同且无重复数字的三位数?比如362算一个,326算一个,请逐个输出他们。


分析

  1. 三层嵌套循环,每一层循环从list中取出一个数字,这三个数字组成一个三位数
  2. 需要对取出来的三个数字进行去重

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding=utf-8
lst = [3,6,2,7]
number_lst = []
for i in lst:
for j in lst:
for k in lst:
if i != j and i != k and j != k: # 去重
number_lst.append(
'{first}{second}{third}'.format(
first=i, second=j, third=k))

print('共有{count}个符合条件的数字'.format(
count=len(number_lst)))
for item in number_lst:
print item

小结

注意字符串format的用法,首先要在字符串里定义哪个区域是需要被替换的,并在大括号里定义一个名字,然后在format函数的参数里指明这个名字被谁替换


完全平方数

  1. 打印10000以内的完全平方数
  2. 如果一个数加100是一个完全平方数,加268也是一个完全平方数,求10000以内所有符合这个要求的数值

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 第一题 方法一
numbers = [x*x for x in range(1, 10001)]
for num in numbers:
print(num)

# 第一题 方法二
num = 1
while num < 10001:
print(num*num)
num += 1

# 第二题
num = 1
squares = []
while True:
square = num*num
if square > 10268:
break
num += 1
squares.append(square)

for square in squares:
if (square + 168) in squares:
print(square - 100)
  • 有一个数,假设加100后的值为A,加268之后的值是B,根据题目要求,A和B都是完全平方数,那么B-A = 168
  • 程序先找出10268以内的所有完全平方数,然后遍历,如果一个完全平方数加168 以后还是完全平方数,那么这个完全平方数减去100,就是要找的数值

打印乘法口诀表

打印乘法口诀表,效果如下表所示:

1
2
3
4
5
6
7
8
9
1*1 = 1  
1*2 = 2 2*2 = 4
1*3 = 3 2*3 = 6 3*3 = 9
1*4 = 4 2*4 = 8 3*4 = 12 4*4 = 16
1*5 = 5 2*5 = 10 3*5 = 15 4*5 = 20 5*5 = 25
1*6 = 6 2*6 = 12 3*6 = 18 4*6 = 24 5*6 = 30 6*6 = 36
1*7 = 7 2*7 = 14 3*7 = 21 4*7 = 28 5*7 = 35 6*7 = 42 7*7 = 49
1*8 = 8 2*8 = 16 3*8 = 24 4*8 = 32 5*8 = 40 6*8 = 48 7*8 = 56 8*8 = 64
1*9 = 9 2*9 = 18 3*9 = 27 4*9 = 36 5*9 = 45 6*9 = 54 7*9 = 63 8*9 = 72 9*9 = 81

解答

1
2
3
4
5
6
7
8
9
10
11
def get_line(number):
line = ""
str_foramt = "{left}*{right} = {result}"
for i in range(1, number+1):
if i != 1:
line += " \t"
line += str_foramt.format(left=i, right=number, result=i*number)
return line

for i in range(1,10):
print(get_line(i))

输出水仙花数

题目

输出所有的水仙花数,所谓水仙花数是指一个三位数各个位上的数的立方相加在一起等于这个三位数。比如153,1的3次方 + 5的三次方 + 3的三次方 等于153。


解答

1
2
3
4
5
6
for n in range(100,1000):
i = n / 100 # 获取百位数
j = n / 10 % 10 # 获取十位数
k = n % 10 # 获取个位数
if n == i**3 + j**3 + k**3:
print(n)

输出学生分数的考评等级

允许用户从终端输入一个分数,程序输出这个分数所属的考评等级,90到100分是A,60到89是B,60分以下是C


解答

1
2
3
4
5
6
7
8
9
10
11
12
while True:
scores = int(input("请输入分数:"))

if scores >= 90 and scores <= 100:
grade = 'A'
elif scores >= 60:
grade = 'B'
elif scores >= 0:
grade = 'C'
else:
break
print(grade)

生成数据

  • 数据可视化:通过可视化表示来探索数据
  • 数据挖掘:使用代码来探索数据集的规律和关联

安装 matplotlib

pip

pip是一个以Python写成的软件包管理系统,它可以安装和管理软件包

大多数较新的Python版本都自带pip,因此首先可检查系统是否已经安装了pip。

  • 在Windows系统中检查是否安装了pip

    打开一个终端窗口,并执行如下命令:

    1
    2
    > python -m pip --version
    pip 20.1.1 from C:\Users\Hunter\AppData\Local\Programs\Python\Python38-32\lib\site-packages\pip (python 3.8)

在 Windows 系统中安装 matplotlib

在Windows系统中,首先需要安装Visual Studio。接下来,访问matplotlib · PyPI,查找与使用的Python版本、操作系统相匹配的.whl文件。

将这个.whl文件复制到项目所在文件夹,打开命令窗口,使用pip来安装matplotlib:

1
python -m pip install --user matplotlib-3.2.2-cp38-cp38-win32.whl

测试 matplotlib

安装必要的包后,对安装进行测试。为此,首先使用命令python或python3启动一个终端会话,再尝试导入matplotlib:

1
2
$ python3
>>> import matplotlib

如果没有出现任何错误消息,就说明成功安装了 matplotlib


绘制简单的折线图

我们将使用平方数序列1、4、9、16和25来绘制这个图表。

1
2
3
4
5
6
7
import matplotlib.pyplot as plt

%matplotlib inline # 让生成的图形嵌入jupyter notebook中

squares = [1, 4, 9, 16, 25]
plt.plot(squares)
plt.show()
  • 模块pyplot包含很多用于生成图表的函数。

  • plt.show()打开matplotlib查看器,并显示绘制的图形。查看器能够缩放和导航图形,另外,单击磁盘图表可将图形保存起来

修改标签文字和线条粗细

上述代码通过matplotlib查看器显示的图形,标签文字太小、线条太细。下面通过一些定制来改善这个图形的可读性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import matplotlib.pyplot as plt

squares = [1, 4, 9, 16, 25]
plt.plot(squares, linewidth=5)

# 设置图表标题,并给坐标轴加上标签
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)

# 设置刻度标记的大小
plt.tick_params(axis='both', labelsize=14)

plt.show()
  • 参数linewidth决定了plot()绘制的线条的粗细
  • 函数title()给图表指定标题
  • 参数fontsize指定了图表中文字的大小
  • 函数xlabel()ylabel()
  • 函数tick_params()设置刻度的样式

校正图形

图形更容易阅读后,我们发现没有正确地绘制数据:折线图的终点指出4.0的平方为25。下面来修复这个问题。

当你向plot()提供一系列数字时,它假设第一个数据点对应的x坐标值为0,但我们的第一个点对应的x值为1。为改变这种默认行为,我们可以给plot()同时提供输入值和输出值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt

input_values = [1, 2, 3, 4, 5]
squares = [1, 4, 9, 16, 25]
plt.plot(input_values, squares, linewidth=5)

# 设置图表标题,并给坐标轴加上标签
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)

# 设置刻度标记的大小
plt.tick_params(axis='both', labelsize=14)

plt.show()

使用 scatter() 绘制散点图并设置其样式

有时候,需要绘制散点图设置各个数据点的样式。例如,你可能想以一种颜色显示较小的值,用另一种颜色显示较大的值。绘制大型数据集时,还可以对每个点都设置相同的样式,再使用不同的样式选项重新绘制某些点,以突出它们

要绘制简单的点,可使用函数scatter(),并向它传递一对x和y坐标,它将在指定位置绘制一个点

1
2
3
4
import matplotlib.pyplot as plt

plt.scatter(2, 4)
plt.show()

下面来设置输出的样式,使其更有趣:添加标题,给轴加上标签,并确保所有文本都大到能够看清

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt

plt.scatter(2, 4, s=200)

# 设置图表标题并给坐标轴加上标签
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)

# 设置刻度标记的大小
plt.tick_params(axis='both', which='major', labelsize=14)
# 显示图例
plt.legend()

plt.show()
  • plt.scatter(2, 4, s=200)实参s设置了绘制图形时使用的点的尺寸

使用 scatter() 绘制一系列点

要绘制一系列的点,可向scatter()传递两个分别包含x值和y值的列表,如下所示:

1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt

x_values = [1, 2, 3, 4, 5]
y_values = [1, 4, 9, 16, 25]

plt.scatter(x_values, y_values, s=100)

# 设置图表标题并给坐标轴加上标签
--snip--

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
--snip--

# 绘制一个展示 船票Fare 与 乘客年龄和性别 之间关系的散点图


# 方式一
# 利用query对性别和年龄进行筛选
x_values_1 = titanic.query("Sex == 'male' & Age != 'NaN'")['Age']
y_values_1 = titanic.query("Sex == 'male' & Age != 'NaN'")['Fare']

x_values_2 = titanic.query("Sex == 'female' & Age != 'NaN'")['Age']
y_values_2 = titanic.query("Sex == 'female' & Age != 'NaN'")['Fare']

plt.scatter(x_values_1, y_values_1, s=10, c='b', label='Male')
plt.scatter(x_values_2, y_values_2, s=10, c='r', label='Female')

# 方式二
titanic.loc[titanic['Sex'] == 'male', 'color'] = 'b' # 该处loc[]的用法为:指出 符合条件的行的列值 和 列标签
titanic.loc[~(titanic['Sex'] == 'male'), 'color'] = 'r'
plt.scatter(titanic['Age'].dropna(), titanic['Fare'].dropna(), s=10, c=titanic[color])



plt.title("船票与年龄", fontsize=24)
plt.xlabel("Age", fontsize=14)
plt.ylabel("Fare", fontsize=14)
# 显示图例
plt.legend()

plt.show()

可利用query()排除一些极端值的干扰

1
2
3
# 可视化 消费金额和购买数量的关系散点图
gu = grouped_user.sum().query('order_amount < 6000')
plt.scatter(gu['order_amount'], gu['order_products'])

自动计算数据

下面是绘制1000个点的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt

x_values = list(range(1, 1001))
y_values = [x**2 for x in x_values]

plt.scatter(x_values, y_values, s=40)

# 设置图表标题并给坐标轴加上标签
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)

# 设置每个坐标轴的取值范围
plt.axis([0, 1100, 0, 1100000])

plt.show()

由于这个数据集较大,我们将点设置得较小,并使用函数axis()指定了每个坐标轴的取值范围


删除数据点的轮廓

matplotlib允许你给散点图中的各个点指定颜色,默认为蓝色点黑色轮廓。在散点图包含的数据点不多时效果很好,但绘制很多点时,黑色轮廓可能会粘连在一起

要删除数据点的轮廓,可在调用scatter()时传递实参edgecolor='none'

plt.scatter(x_values, y_values, edgecolor='none', s=40)

注意:matplotlib 2.0.0版本之后,scatter()函数的实参edgecolor默认为’none'


自定义颜色

修改数据点的颜色,可向scatter()传递参数c,并将其设置为要使用的颜色的名称,如下所示:

plt.scatter(x_values, y_values, c='red', s=40)

还可以使用RGB颜色模式自定义颜色:

plt.scatter(x_values, y_values, c=(0, 0, 0.8), s=40)

值越接近0,指定的颜色越深,值越接近1,指定的颜色越浅


使用颜色映射

颜色映射(colormap)是一系列颜色,它们从起始颜色渐变到结束颜色。在可视化中,颜色映射用于突出数据的规律,例如你可能用较浅的颜色来显示较小的值,并用较深的颜色来显示较大的值。

模块pyplot内置了一组颜色映射

1
2
3
4
5
6
7
8
9
10
import matplotlib.pyplot as plt

x_values = list(range(1, 1001))
y_values = [x**2 for x in x_values]

plt.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Blues,
s=40)

# 设置图表标题并给坐标轴加上标签
--snip--

我们将参数c设置成了一个y值列表,并使用参数cmap告诉pyplot使用哪个颜色映射。y值较小的点显示为浅蓝色,y值较大的点显示为深蓝色。

注意:要了解pyplot中所有的颜色映射,访问Colormap reference


自动保存图表

要让程序自动将图表保存到文件中,可将对plt.show()的调用替换为对plt.savefig()的调用:

plt.savefig('suqares_plot.png', bbox_inches='tight')

  • 第一个实参指定要以什么样的文件名保存图表,这个文件将存储到当前程序所在的目录中
  • 第二个实参指定将图表多余的空白区域剪掉。如果要保留图表周围多余的空白区域,可省略这个实参

绘制饼图

matplotlib可视化饼图 - 知乎

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
# 绘制一个展示男女乘客比例的扇形图
males = (titanic['Sex'] == 'male').sum()
females = (titanic['Sex'] == 'female').sum()

proportions = [males, females]

# 绘制饼图
plt.pie(
proportions,

labels = ['Males', 'Females'],

#是否 添加阴影效果
shadow = False,

# 指定填充色
colors = ['blue', 'red'],

# 每一块偏移中心的距离
explode = (0.15 , 0.3),

# 饼图的初始摆放角度,默认图是从x轴正方向逆时针画起,如设定=90则从y轴正方向画起
startangle = 90,

# 显示百分比, 小数点前后表示最少的位数 末尾两个%%是输出%自身
autopct = '%1.1f%%'

)


# x,y轴刻度设置一致,保证饼图为圆形
plt.axis('equal')

# 设置图表标题
plt.title("男女乘客比例")

# tight_layout会自动调整子图参数,使之填充整个图像区域
plt.tight_layout()

plt.show()

随机漫步

随机漫步是这样行走得到的路径每次行走都完全是随机的,没有明确的方向,结果是由一系列随机决策决定的。在自然界、物理学、生物学、化学和经济领域,随机漫步都有其实际用途。例如,漂浮在水滴上的花粉因不断受到水分子的挤压而在水面上移动。水滴中的分子运动是随机的,因此花粉在水面上的运动路径犹如随机漫步。

创建 RandomWalk() 类

创建一个RandomWalk()类,这个类需要三个属性:

  1. 存储随机漫步次数的变量
  2. 随机漫步经过的每个点的x坐标
  3. 随机漫步经过的每个点的y坐标

RandomWalk()类只包含两个方法:

  1. __init__()
  2. fill_walk():计算随机漫步经过的所有点

先来看看__init__():

1
2
3
4
5
6
7
8
9
10
11
12
from random import choice

class RandomWalk():
"""一个生成随机漫步数据的类"""

def __init__(self, num_points=5000):
"""初始化随机漫步的属性"""
self.num_points = num_points

# 所有随机漫步都始于(0, 0)
self.x_values = [0]
self.y_values = [0]

为做出随机决策,我们将所有可能的选择都存储在一个列表中,并在每次做决策时都使用choice()来决定使用哪种选择


选择方向

我们将使用fill_walk()来生成漫步包含的点,并决定每次漫步的方向,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def fill_walk(self):
"""计算随机漫步包含的所有点"""

# 不断漫步,直到列表到达指定的长度
while len(self.x_values) < self.num_points:
# 决定前进方向以及沿这个方向前进的距离
x_direction = choice([1, -1])
x_distance = choice([0, 1, 2, 3, 4])
x_step = x_direction * x_distance

y_direction = choice([1, -1])
y_distance = choice([0, 1, 2, 3, 4])
y_step = y_direction * y_distance

# 拒绝原地踏步
if x_step == 0 and y_step == 0:
continue

# 计算下一个点的x和y值
next_x = self.x_values[-1] + x_step
next_y = self.y_values[-1] + y_step

self.x_values.append(next_x)
self.y_values.append(next_y)
  • choice([-1, 1])从-1和1中随机选择一个值
  • choice([0, 1, 2, 3, 4])从0~4中随机选择一个值

绘制随机漫步图

rw_visual.py

1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt

from random_walk import RandomWalk

# 创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()
plt.scatter(rw.x_values, rw.y_values, s=15)
plt.show()

模拟多次随机漫步

要在不多次运行程序的情况下模拟多次随机漫步,一种办法是将这些代码放在一个while循环中,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt

from random_walk import RandomWalk

# 只要程序处于活动状态,就不断地模拟随机漫步
while True:
# 创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()
plt.scatter(rw.x_values, rw.y_values, s=15)
plt.show()

keep_running = input("Make another walk? (y/n): ")
if keep_running == 'n':
break

给点着色

我们将使用颜色映射来指出漫步中各点的先后顺序。为根据漫步中各点的先后顺序进行着色,我们传递参数c,并将其设置为一个列表,其中包含各点的先后顺序

由于这些点是按顺序绘制的,因此给参数c指定的列表只需包含数字1~5000

1
2
3
4
5
6
7
8
9
10
11
12
--snip--
while True:
# 创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()

point_numbers = list(range(rw.num_points))
plt.scatter(rw.x_values, rw.y_values, c=point_numbers, cmap=plt.cm.Blues, s=15)
plt.show()

keep_running = input("Make another walk? (y/n): ")
--snip--

重新绘制起点和终点

1
2
3
4
5
6
7
8
9
10
11
--snip--
while True:
--snip--
plt.scatter(rw.x_values, rw.y_values, c=point_numbers, cmap=plt.cm.Blues, s=15)

# 突出起点和终点
plt.scatter(0, 0, c='green', s=100)
plt.scatter(rw.x_values[-1], rw.y_values[-1], c='red', s=100)

plt.show()
--snip--

隐藏坐标轴

1
2
3
4
5
6
7
8
9
10
11
--snip--
while True:
--snip--
plt.scatter(rw.x_values[-1], rw.y_values[-1], c='red', s=100)

# 隐藏坐标轴
plt.axes().get_xaxis().set_visible(False)
plt.axes().get_yaxis().set_visible(False)

plt.show()
--snip--

为修改坐标轴,使用了函数plt.axes()来将每条坐标轴的可见性设置为False。


增加点数

在创建RandomWalk实例时增大num_points的值,并在绘图时调整每个点的大小,如下所示:

1
2
3
4
5
6
7
8
9
10
--snip--
while True:
# 创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk(5000)
rw.fill_walk()

# 绘制点并将图形显示出来
point_numbers = list(range(rw.num_points))
plt.scatter(rw.x_values, rw.y_values, c=point_numbers, cmap=plt.cm.Blues, s=1)
--snip--

调整尺寸以适合屏幕

图表适合屏幕大小时,更能有效地将数据中的规律呈现出来。为让绘图窗口更适合屏幕大小,可像下面这样调整matplotlib输出的尺寸

1
2
3
4
5
6
7
8
9
--snip--
while True:
# 创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()

# 设置绘图窗口的尺寸
plt.figure(figsize=(10, 6))
--snip--

函数figure()用于指定图表的宽度、高度、分辨率和背景色

需要给形参figsize指定一个元组,向matplotlib指出绘图窗口的尺寸单位为英寸

Python假定屏幕分辨率为80像素/英寸,如果知道自己系统的分辨率,可使用形参dpi向figure()传递该分辨率,以有效利用可用的屏幕空间:

plt.figure(dpi=128, figsize=(10, 6))


使用 Pygal 模拟掷骰子

本节将使用Python可视化包Pygal来生成可缩放的矢量图形文件,它们将自动缩放,以适合观看者的屏幕。如果你打算以在线的方式使用图表,请考虑使用Pygal来生成它们,这样在任何设备上显示都会很美观。

在这个项目中,我们将对掷骰子的结果进行分析。为确定哪些点数出现的可能性最大,我们将生成一个表示掷骰子结果的数据集,并根据结果绘制出一个图形

安装 Pygal

在 Windows 系统中,命令:

python -m pip install --user pygal==1.7


Paygal 画廊

要了解使用Pygal可创建什么样的图表,可以查看Chart types — pygal documentation


创建 Die 类

下面的类模拟掷一个骰子:

1
2
3
4
5
6
7
8
9
10
11
12
from random import randint

class Die():
"""表示一个骰子的类"""

def __init__(self, num_sides=6):
"""骰子默认为6面"""
self.num_sides = num_sides

def roll(self):
"""返回一个位于1和骰子面数之间的随机数"""
return randint(1, self.num_sides)
  • 方法roll()使用函数randint()返回一个1和面数之间的随机整数(包括起始值1和终止值num_sides)

骰子根据面数命名,6面的骰子命名为D6,8面的骰子命名为D8。


掷骰子

使用这个类来创建图表前,先来掷D6骰子,将结果打印出来,并检查结果是否合理:

1
2
3
4
5
6
7
8
9
10
11
12
from die import Die

# 创建一个D6
die = Die()

# 掷几次骰子,并将结果存储在一个列表中
results = []
for roll_num in range(100):
result = die.roll()
results.append(result)

print(results)

分析结果

为分析掷一个D6骰子的结果,我们计算每个点数出现的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--snip--
# 掷几次骰子,并将结果存储在一个列表中
results = []
for roll_num in range(1000):
result = die.roll()
results.append(result)

# 分析结果
frequencies = []
for value in range(1, die.num_sides+1):
frequency = results.count(value)
frequencies.append(frequency)

print(frequencies)
  • 为分析结果,我们创建了空列表frequencies,用于存储每种点数出现的次数

绘制条形图(bar chart)

有了频率列表后,我们就可以绘制一个表示结果的条形图(bar chart)指出各种结果出现的频率

  1. 利用pygal绘制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pygal
--snip--

# 分析结果
frequencies = []
for value in range(1, die.num_sides+1):
frequency = results.count(value)
frequencies.append(frequency)

# 对结果进行可视化
bar = pygal.Bar()

bar.title = "Results of rolling one D6 1000 times."
bar.x_labels = ['1', '2', '3', '4', '5', '6']
bar.x_title = "Result"
bar.y_title = "Frequency of Result"

bar.add('D6', frequencies)
bar.render_to_file('die_visual.svg')
  • 为创建**条形图(bar diagram)**,创建了一个pygal.Bar()实例
  • 将D6骰子的可能结果用作x轴的标签
  • 使用add()将一系列值添加到图表中(传递给添加的值指定的标签将出现在图表中的值的列表)
  • 将图表渲染为一个SVG文件(Scalable Vector Graphics,可缩放的矢量图形,是一种基于XML图像文件格式)

要查看生成的直方图,最简单的方式是使用Web浏览器。为此,在任意浏览器中新建一个标签页,再打开文件die_visual.svg,将看到对应的图表。

注意:Python让这个图表具有交互性:如果将鼠标指向该图表中的任何条形,将看到与之相关联的数据(若无效果,刷新)。


绘制直方图(histogram)

  1. 利用pyplot绘制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
import matplotlib.pyplot as plt
# 绘制一个展示船票价格的直方图

# 创建直方图
# 参数:需要计算的series ,bins可以是直方图个数(默认为10),也可以是自定义的序列
plt.hist(titanic['Fare'], bins = range(0,600,10))

# Set the title and labels
plt.xlabel('Fare')
plt.ylabel('Frequency')
plt.title('船票价格分布')

# show the plot
plt.show()


# 用户消费金额的分布图,利用query消除极值对可视化呈现效果的干扰
plt.hist(grouped_user.sum().query('order_amount < 1000')['order_amount'], bins = 20)
plt.show()

同时掷两个骰子

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
import pygal

from die import Die

# 创建两个D6骰子
die_1 = Die()
die_2 = Die()

# 掷骰子多次,并将结果存储到一个列表中
results = []
for roll_num in range(1000):
result = die_1.roll() + die_2.roll()
results.append(result)

# 分析结果
frequencies = []
max_result = die_1.num_sides + die_2.num_sides
for value in range(2, max_result+1):
frequency = results.count(value)
frequencies.append(frequency)

# 可视化结果
bar = pygal.Bar()

bar.title = "Results of rolling two D6 dice 1000 times."
bar.x_labels = ['2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
bar.x_title = "Result"
bar.y_title = "Frequency of Result"

bar.add('D6 + D6', frequencies)
bar.render_to_file('dice_visual.svg')

同时掷两个面数不同的骰子

下面来创建一个6面骰子和一个10面骰子,看看同时掷这两个骰子50000次的结果:

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
from die import Die

import pygal

# 创建一个D6和一个D10
die_1 = Die()
die_2 = Die(10)

# 掷骰子多次,并将结果存储到一个列表中
results = []
for roll_num in range(50000):
result = die_1.roll() + die_2.roll()
results.append(result)

# 分析结果
frequencies = []
max_result = die_1.num_sides + die_2.num_sides
for value in range(2, max_result+1):
frequency = results.count(value)
frequencies.append(frequency)

# 可视化结果
bar = pygal.Bar()

bar.title = "Results of rolling a D6 and a D10 50,000 times."
bar.x_labels = [str(value) for value in range(2, 17)]
bar.x_title = "Result"
bar.y_title = "Frequency of Result"

bar.add('D6 + D10', frequencies)
bar.render_to_file('dice_visual.svg')
  • 通过列表解析调整x轴标签

下载数据

网上的数据多得难以置信,且大多未经过仔细检查。如果能够对这些数据进行分析,你就能发现别人没有发现的规律和关联。

CSV 文件格式

要在文本文件中存储数据,最简单的方式是将数据作为一系列以逗号分隔的值(CSV, Comma-Separated Values)写入文件,这样的文件称为CSV文件

例如,下面是一行CSV格式的天气数据:

2014-1-5,61,44,26,18,7,-1,56,30,9,30.34,30.27,30.15,,,,10,4,,0.00,0,,195

CSV文件对人来说阅读起来比较麻烦,但程序可轻松地提取并处理其中的值,这有助于加快数据分析过程。

  • 最好使用用文字编辑器打开csv文件查看Excel可能会更改显示格式

分析 CSV 文件头

csv模块包含在Python标准库中,可用于分析CSV文件中的数据行,让我们能够快速提取感兴趣的值。下面先来看一下sitka_weather_07-2014.csv文件的第一行,其中包含一系列有关数据的描述

1
2
3
4
5
6
7
import csv

filename = 'sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
print(header_row)
  • 调用csv.reader(),并将前面存储的文件对象作为实参传入,从而创建一个与该文件相关联的阅读器对象
  • 模块csv的reader类包含next()方法,调用内置函数next()将一个阅读器对象作为参数传入,将调用阅读器对象的next()方法,从而返回文件中的下一行
  • reader处理文件中以逗号分隔的第一行数据,并将每项数据都作为一个元素存储在列表中

注意:文件头的格式并非总是一致的, 空格和单位可能出现在奇怪的地方,但不会带来任何问题


打印文件头及其位置

为让文件头数据更容易理解,将列表中的每个文件头及其位置打印出来:

1
2
3
4
5
6
7
--snip--
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)

for index, column_header in enumerate(header_row):
print(index, column_header)
  • 对列表调用了enumerate()(枚举)来获取每个元素的索引及其值

提取并读取数据

首先读取每天的最高气温

1
2
3
4
5
6
7
8
9
10
11
12
13
import csv

# 从文件中获取最高气温
filename = 'sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)

highs = []
for row in reader:
highs.append(row[1])

print(highs)
  • for row in reader遍历文件中余下的各行

    每次执行上述循环,我们都将索引1处(第2列)的数据附加到highs末尾。

下面使用int()将这些字符串转换为数字让matplotlib能够读取它们

1
2
3
4
5
6
7
--snip--
highs = []
for row in reader:
high = int(row[1])
highs.append(high)

print(highs)

绘制气温图表

为可视化这些气温数据,我们首先使用matplotlib创建一个显示每日最高气温的简单图形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import csv

from matplotlib import pyplot as plt

# 从文件中获取最高气温
--snip--

# 根据数据绘制图形
plt.figure(dpi=128, figsize=(10, 6))
plt.plot(highs, c='red')

# 设置图形的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlabel('', fontsize=16)
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)

plt.show()
  • 鉴于还没有添加日期,因此没有给x轴添加标签

  • tick_params()

    which一共3个参数:[‘major’ , ‘minor’. ‘both’]
    默认是major,表示主刻度,后面分布为次刻度主次刻度都显示.

  • 调整x轴日期的显示间隔

    1
    plt.xticks(pd.date_range('1997-01-01', '1997-04-01', freq='10d'))

模块 datetime

下面在图表中添加日期。在天气数据文件中,第一个日期在第二行

获取日期数据时,获得的是一个字符串,因此需要将字符串'2014-7-1'转换为一个表示相应日期的对象。可使用模块datetime中的方法strptime()

1
2
3
4
>>> from datetime import datetime
>>> first_date = datetime.strptime('2014-7-1', '%Y-%m-%d')
>>> print(first_date)
2014-07-01 00:00:00
  • 传入方法strptime()第2个实参告诉Python如何设置日期的格式

    • %Y-让Python将字符串中第一个连字符前面的部分视为4位的年份
    • %m-让Python将第二个连字符前面的部分视为表示月份的数字
    • %d让Python将最后一部分视为月份中的一天
  • 方法strptime()可接受各种实参,并根据它们来决定如何解读日期:

    实参 含义
    %A 星期的名称,如Monday
    %B 月份名,如January
    %m 数字表示的月份 (01~12)
    %d 数字表示月份中的一天 (01~31)
    %Y 四位的年份,如2015
    %y 两位的年份,如15
    %H 24小时制的小时数 (00-23)
    %I 12小时制的小时数 (01~12)
    %p am或pm
    %M 分钟数 (00~59)
    %S 秒数 (00~61)

在图表中添加日期

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
import csv
from datetime import datetime

from matplotlib import pyplot as plt

# 从文件中获取日期和最高气温
filename = 'sitka_weather_07-2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)

dates, highs = [], []
for row in reader:
current_date = datetime.strptime(row[0], "%Y-%m-%d")
dates.append(current_date)

high = int(row[1])
highs.append(high)

# 根据数据绘制图形
plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red')

# 设置图形的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlabel('', fontsize=16)
plt.gcf().autofmt_xdate() # 绘制斜的日期标签,以免它们彼此重叠
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)

plt.show()
  • 创建了两个空列表:dates, highs = [], []
  • 将包含日期信息的数据转换为datetime对象:datetime.strptime(row[0], "%Y-%m-%d")
  • 将日期和最高气温值传给plot()plt.plot(dates, highs, c='red')

涵盖更长的时间

创建覆盖整年的天气图:

1
2
3
4
5
6
7
8
9
--snip--
# 从文件中获取日期和最高气温
filename = 'stika_weather_2014.csv'
with open(filename) as f:
--snip--
# 设置图形的格式
plt.title("Daily high temperatures - 2014", fontsize=24)
plt.xlabel('', fontsize=16)
--snip--

再绘制一个数据系列

从数据文件中提取最低气温,并添加到图表中:

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
--snip--
# 从文件中获取日期、最高气温和最低气温
filename = 'sitka_weather_2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)

dates, highs, lows = [], [], []
for row in reader:
current_date = datetime.strptime(row[0], "%Y-%m-%d")
dates.append(current_date)

high = int(row[1])
highs.append(high)

low = int(rpw[3])
lows.append(low)

# 根据数据绘制图形
plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red')
plt.plot(dates, lows, c='blue')

# 设置图形的格式
plt.title("Daily high and low temperatures - 2014", fontsize=24)
--snip--

给图表区域着色

通过着色来呈现每天的气温范围,为此使用方法fill_between(),它接受一个x值系列和两个y值系列,并填充两个y值系列之间的空间

1
2
3
4
5
6
--snip--
# 根据数据绘制图形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red'. alpha=0.5)
plt.plot(dates, lows, c='blue'. alpha=0.5)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)
  • 实参alpha指定颜色的透明度
    • 0表示完全透明
    • 1(默认)表示完全不透明
  • fill_between()
    • 实参facecolor指定了填充区域的颜色

错误检查

我们应该能够使用有关任何地方的天气数据来运行highs_lows.py中的代码,但有些气象站会偶尔出现故障,未能收集部分或全部其应该收集的数据缺失数据可能会引发异常,如果不妥善地处理,还可能导致程序崩溃

例如,我们来看看生成加利福尼亚死亡谷的气温图时出现的情况:

1
2
3
4
5
--snip--
# 从文件中获取日期、最高气温和最低气温
filename = 'death_valley_2014.csv'
withj open(filename) as f:
--snip--

运行结果:

1
2
3
4
Traceback (most recent call last):
File "highs_lows.py", line 17, in <module>
high = int(row[1])
ValueError: invalid literal for int() with base 10: ''

查看文件可知,没有记录2014年2月16日的数据,表示最高温度的字符串为空。为解决这种问题,我们从CSV文件中读取值时执行错误检查代码,对分析数据集时可能出现的异常进行处理

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
--snip--
# 从文件中获取日期、最高气温和最低气温
filename = 'death_valley_2014.csv'
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)

dates, highs, lows = [], [], []
for row in reader:
try:
current_date = datetime.strptime(row[0], "%Y-%m-%d")
high = int(row[1])
low = int(row[3])
except ValueError:
print(current_date, 'missing data')
else:
dates.append(current_date)
highs.append(high)
lows.append(low)

# 根据数据绘制图形
--snip--

# 设置图形的格式
title = "Daily high and low temperatures - 2014\nDeath Valley, CA"
plt.title(title, fontsize=20)
--snip--

在有些情况下,需要使用continue来跳过一些数据,或者使用remove()(删除列表值)或del(删除 键-值 对)将已提取的数据删除。可采用任何管用的方法,只要能进行精确而有意义的可视化就好。


16-2 比较锡特卡和死亡谷的气温:

为准确地比较锡特卡和死亡谷的气温范围,在y轴上使用相同的刻度,对两地的气温范围进行直接比较:

  • pyplot的方法ylim()可以对y轴的刻度做限制xlim()则可以对x轴的刻度做限制。
1
2
3
4
5
# 设置图形的格式
--snip--
plt.ylim(10, 120)

plt.show()

尝试在一个图表中呈现这两个数据集

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
import csv
from datetime import datetime

from matplotlib import pyplot as plt

def get_weather_data(filename, dates, highs, lows):
"""从文件中获取日期、最高气温和最低气温"""
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)

for row in reader:
try:
current_date = datetime.strptime(row[0], "%Y-%m-%d")
high = int(row[1])
low = int(row[3])
except ValueError:
print(current_date, 'missing data')
else:
dates.append(current_date)
highs.append(high)
lows.append(low)

# 获取锡特卡的气温数据
dates, highs, lows = [], [], []
get_weather_data('stika_weather_2014.csv', dates, highs, lows)

# 根据锡特卡数据绘制图形
plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates, highs, c='red', alpha=0.6)
plt.plot(dates, lows, c='blue', alpha=0.6)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.15)

# 获取死亡谷的气温数据
dates, highs, lows = [], [], []
get_weather_data('death_valley_2014.csv', dates, highs, lows)

# 将死亡谷数据的图形 添加到当前的绘制中
plt.plot(dates, highs, c='red', alpha=0.3)
plt.plot(dates, lows, c='blue', alpha=0.3)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.05)

# 设置图形的格式
title = "Daily high and low temperatures - 2014"
title += "\nSitka, AK and Death Valley, CA"
plt.title(title, fontsize=20)
plt.xlabel('', fontsize=16)
plt.gcf().autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.ylim(10, 120)

plt.show()

制作交易收盘价走势图: JSON 格式

下载收盘价数据

btc_close_2017.json实际是一个很长的Python列表,其中每个元素都是一个包含五个键的字典:统计日期、月份、周数、周几以及收盘价。由于2017年1月1日是周日,作为2017年的第一周实在太短,。因此被计入2016年的第52周。于是2017年的第一周是从1月2日开始的。

可以将收盘价数据文件直接下载到程序所在的文件夹中,也可以用Python 2.x标准库中模块urllib2(Python 3.x版本使用urllib)的函数urlopen()来做,还可以通过Python的第三方模块requests下载数据。

如果采用函数urlopen()来下载数据,可以使用如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from __future__ import (absolute_import, division, print_function, unicode_literals)

try:
# Python 2.x 版本
from urllib2 import urlopen
except ImportError:
# Python 3.x 版本
from urllib.request import urlopen
import json

json_url = 'https://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json'
response = urlopen(json_url)
# 读取数据
req = response.read()
# 将数据写入文件
with open('btc_close_2017_urllib.json', 'wb') as f:
f.write(req)
# 加载json格式
file_urllib = json.loads(req)
print(file_urllib)
  • python打开文件时w与wb的区别,r与rb的区别

    Windows中的换行符\r\n

    • 文本方式('w')写入,遇到\n自动替换\r\n
    • 二进制文本方式('wb')写入,遇到\n仍然按\n记录
  • json

    • load()

      载入json文件

    • loads()

      载入JSON格式的字符串

函数urlopen的代码稍微复杂一些,第三方模块requests封装了许多常用的方法,让数据下载和读取方式变得非常简单

1
2
3
4
5
6
7
8
import requests

json_url = 'https://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json'
req = requests.get(json_url)
# 将数据写入文件
with open('btc_close_2017_request.json', 'w') as f:
f.write(req.text)
file_requests = req.json()
  • req.text属性可以直接读取文件数据返回格式是字符串
  • req.json()可以将json文件的数据转换为Python列表,与之前的file_urllib内容相同

提取相关的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import json

# 将数据加载到一个列表中
filename = 'btc_close_2017.json'
with open(filename) as f:
btc_data = json.load(f)
# 打印每一天的信息
for btc_dict in btc_data:
date = btc_dict['date']
month = btc_dict['month']
week = btc_dict['week']
weekday = btc_dict['weekday']
close = btc_dict['close']
print("{} is month {} week {}, {}, the close price is {} RMB".format(date, month, week, weekday, close))

将字符串转换为数字值

btc_close_2017.json中的每个键和值都是字符串。为了能在后边的内容中对交易数据进行计算,需要先将表示周数和收盘价的字符串转换为数值,因此使用函数int()

1
2
3
4
5
6
7
8
9
10
--snip--

# 打印每一天的信息
for btc_dict in btc_data:
date = btc_dict['date']
month = int(btc_dict['month'])
week = int(btc_dict['week'])
weekday = btc_dict['weekday']
close = int(float(btc_dict['close']))
print("{} is month {} week {}, {}, the close price is {} RMB".format(date, month, week, weekday, close))
  • 在实际工作中,原始数据的格式经常是不统一的。此类数值类型转换造成的ValueError异常十分普遍。该例子中,无法将包含小数点的字符串转换为整数,因此需要先转换为浮点数再转换为整数int(float(btc_dict['close']))

有了这些数据之后,可以结合Pygal的可视化功能来探索一些有趣的信息


绘制收盘价折线图

之前章节介绍过用Pygal绘制条形图(bar chart)的方法,也介绍了用matplotlib绘制折线图(line chart)的方法。下面用Pygal来实现收盘价的折线图

绘制折线图之前,需要获取x轴与y轴数据,因此我们创建了几个列表来存储数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--snip--

# 创建5个列表,分别存储日期和收盘价
dates = []
months = []
weeks = []
weekdays = []
close = []
# 每一天的信息
for btc_dict in btc_data:
dates.append(btc_dict['date'])
months.append(int(btc_dict['month']))
weeks.append(int(btc_dict['week']))
weekdays.append(btc_dict['weekday'])
close.append(int(float(btc_dict['close'])))

由于数据点比较多,x轴要显示346个日期,因此需要利用Pygal的配置参数,对图形进行适当的调整

1
2
3
4
5
6
7
8
9
10
11
--snip--

import pygal

line_chart = pygal.Line(x_label_rotation=20, show_mirror_x_labels=False)
line_chart.title = '收盘价(¥)'
line_chart.x_labels = dates
N = 20 # x轴坐标每隔20天显示一次
line_chart.x_labels_major = dates[::N]
line_chart.add('收盘价', close)
line_chart.render_to_file('收盘价折线图(¥).svg')
  • pygal.Line()创建Line实例
    • x_label_rotation=20:让x轴上的标签顺时针旋转20°
    • show_mirror_x_labels=False:告诉图形不用显示所有的x轴标签
  • line_chart.x_labels_major = dates[:20]:x轴坐标每隔20天显示一次

下面对价格做一些简单的探索


时间序列特征初探

进行时间序列分析总是期望发现趋势(trend)周期性(seasonality)噪声(noise),从而能够描述事实、预测未来、做出决策

从收盘价的折线图可以看出,2017年的总体趋势时非线性的,而且增长幅度不断增大,似乎呈指数分布。但是,在每个季度末(3月、6月、9月)似乎有一些相似的波动为了验证周期性的假设,需要先将非线性的趋势(指数增长部分)消除。**对数变换(log transformation)**是常用的处理方法之一。

Python标准库的数学模块math来解决这个问题。math里有许多常用的数学函数,这里以10为底的对数函数math.log10计算收盘价,日期仍然保持不变——这种方式称为半对数(semi-logarithmic)变换

1
2
3
4
5
6
7
8
9
10
11
12
13
--snip--

import pygal
import math

line_chart = pygal.Line(x_label_rotation=20, show_mirror_x_labels=False)
line_chart.title = '收盘价对数变换(¥)'
line_chart.x_labels = dates
N = 20 # x轴坐标每隔20天显示一次
line_chart.x_labels_major = dates[::N]
close_log = [math.log10(_) for _ in close]
line_chart.add('log收盘价', close_log)
line_chart.render_to_file('收盘价对数变换折线图(¥).svg')

剔除非线性趋势之后,整体的趋势更接近线性增长,且收盘价在每个季度末似乎有显著的周期性。那么,12月会不会再现这一场景?下面来看看收盘价的月日均值周日均值的表现。


收盘价均值

再利用btc_close_2017.json文件中的数据,绘制2017年前11个月的日均值、前49周(01-02~12-10)的日均值,以及每周各天的日均值。虽然这些日均值的数值不同,但都是一段时间的均值,计算方法都是一样的。因此,可以将绘图代码封装成函数,以便重复使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--snip--

from itertools import groupby

def draw_line(x_data, y_data, title, y_legend):
xy_map = []
for x, y in groupby(sorted(zip(x_data, y_data)), key=lambda _: _[0]):
y_list = [v for _, v in y]
xy_map.append([x, sum(y_list) / len(y_list)])
x_unique, y_mean = [*zip(*xy_map)]
line_chart = pygal.Line()
line_chart.title = title
line_chart.x_labels = x_unique
line_chart.add(y_legend, y_mean)
line_chart.render_to_file(title+'.svg')
return line_chart
  • 由于需要将数据按月份、周数、周几分组,再计算每组的均值,因此导入Python标准库中模块itertools的函数groupby
  • 将x轴与y轴的数据合并排序,再用函数groupby分组
  • 分组之后,求出每组的平均值,存储到xy_map中
  • 最后,将xy_map中存储的x轴与y轴数据分离,就可以像之前一样用Pygal画图了

下面画出收盘价月日均值,由于12月的数据不完整,只取1月到11月的数据。通过dates查找2017-12-01索引的位置,确定周数和收盘价的取值范围

1
2
3
4
idx_month = dates.index('2017-12-01')
line_chart_month = draw_line(months[:idx_month], close[:idx_month], '收盘价月日均值(¥)',
'月日均值')
line_chart_month

收盘价数据仪表盘

每个SVG文件打开之后都是独立的页面,如果能整合在一起,就可以方便地进行长期管理、监测和分析。另外,新的图表也可以方便地加入进来,这样就形成了一个数据仪表盘(dashboard)。下面,将之前绘制的图整合起来,做一个收盘价数据仪表盘:

1
2
3
4
5
6
7
8
9
10
11
12
--snip--

with open('收盘价Dashboard.html', 'w', encoding='utf8') as html_file:
html_file.write(
'<html><head><title>收盘价Dashboard</title><meta charset="utf-8"></head><body>\n')
for svg in [
'收盘价折线图(¥).svg', '收盘价对数变换折线图(¥).svg', '收盘价月日均值(¥).svg',
'收盘价周日均值(¥).svg', '收盘价星期均值(¥).svg'
]:
html_file.write(
' <object type="image/svg+xml" data="{0}" height=500></object>\n'.format(svg))
html_file.write('</body></html>')

和常见网络应用的数据仪表盘一样,这个数据仪表盘也是一个完整的网页。


使用 API

使用 Web API

Web API是网站的一部分,用于 与使用非常具体的URL请求特定信息的程序 交互。这种请求称为API调用。请求的数据将以易于处理的格式(如JSON或CSV)返回

依赖于外部数据源的大多数应用程序都依赖于API调用,如集成社交媒体网站的应用程序。

Git 和 GitHub

本章的可视化将基于来自GitHub的信息。我们将使用GitHub的API来请求有关该网站中Python项目的信息,然后使用Pygal生成交互式可视化,以呈现这些项目的受欢迎程度

本章将编写一个程序,它自动下载GitHub上星级最高的Python项目的信息,并对这些信息进行可视化


使用 API 调用请求数据

GitHub的API让你能够通过API调用来请求各种信息。要知道API调用是什么样的,在浏览器中访问如下地址:

1
https://api.github.com/search/repositories?q=language:python&sort=starshttps://api.github.com/
  • 第一部分https://api.github.com/:将请求发送到GitHub网站中响应API调用的部分
  • 接下来的部分search/repositories:让API搜索GitHub上的所有仓库
  • repositories后面的问号指出我们要传递一个实参
    • q=q表示查询等号让我们能够开始指定查询
    • 通过使用language:python,我们指出只想获取主要语言为Python的仓库的信息
  • 最后一部分&sort=stars:指定将项目按其获得的星级排序

安装 requests

requests包让Python程序能够轻松地向网站请求信息以及检查返回的响应。要安装requests,执行:

1
pip install --user requests

处理 API 响应

下面来编写一个程序,它执行API调用并处理结果,找出GitHub上星级最高的Python项目:

1
2
3
4
5
6
7
8
9
10
11
12
import requests

# 执行API调用并存储响应
url = 'https://api.github.com/search/repositories?=language:python&sort=stars'
r = requests.get(url)
print("Status code:", r.status_code)

# 将API响应存储在一个变量中
response_dict = r.json()

# 处理结果
print(response_dict.keys())
  • 使用requests.get()执行API调用

    响应对象包含一个名为status_code的属性

    • 状态码200表示请求成功
  • 这个API返回JSON格式的信息。因此我们使用方法json()将这些信息转换为一个Python字典


处理响应字典

下面来生成一些概述API调用返回的信息的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

# 执行API调用并存储响应
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
r = requests.get(url)
print("Status code:", r.status_code)

# 将API响应存储在一个变量中
response_dict = r.json()
print("Total repositories:", response_dict['total_count'])

# 探索有关仓库的信息
repo_dicts = response_dict['items']
print("Repositories returned:", len(repo_dicts))

# 研究第一个仓库
repo_dict = repo_dicts[0]
print("\nKeys:", len(repo_dict))
for key in sorted(repo_dict.keys()):
print(key)
  • 'items'关联的值是一个列表,其中包含很多字典,每个字典都包含有关一个Python仓库的信息

下面来提取repo_dict中与一些键相关联的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
--snip--

# 研究第一个仓库
repo_dict = repo_dicts[0]

print("\nSelected information about first repository:")
print('Name:', repo_dict['name'])
print('Owner:', repo_dict['owner']['login'])
print('Stars:', repo_dict['stargazers_count'])
print('Reposiory:', repo_dict['html_url'])
print('Created:', repo_dict['created_at'])
print('Updated:', repo_dict['updated_at'])
print('Description:', repo_dict['description'])

概述最受欢迎的仓库

对这些数据进行可视化时,我们需要涵盖多个仓库。下面就来编写一个循环,打印API调用返回的每个仓库的特定信息,以便能够在可视化中包含所有这些信息:

1
2
3
4
5
6
7
8
9
10
11
12
--snip--
# 研究有关仓库的信息
repo_dicts = response_dict['items']
print("Repositories returned:", len(repo_dicts))

print("\nSelected information about each repository:")
for repo_dict in repo_dicts:
print('\nName:', repo_dict['name'])
print('Owner:', repo_dict['owner']['login'])
print('Stars:', repo_dict['stargazers_count'])
print('Reposiory:', repo_dict['html_url'])
print('Description:', repo_dict['description'])

监视 API 的速率限制

大多数API都存在速率限制,即你在特定时间内可执行的请求数存在限制。要获悉你是否接近了GitHub的限制,在浏览器中输入https://api.github.com/rate_limit,将得到类似于下面的响应:

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
{
"resources":{
"core":{
"limit":60,
"remaining":0,
"reset":1593698255
},
"graphql":{
"limit":0,
"remaining":0,
"reset":1593700880
},
"integration_manifest":{
"limit":5000,
"remaining":5000,
"reset":1593700880
},
"search":{
"limit":10,
"remaining":10,
"reset":1593697340
}
},
"rate":{
"limit":60,
"remaining":0,
"reset":1593698255
}
}

我们关心的信息是搜索API的速率限制。从18~22行可知,极限为每分钟10个请求;当前这一分钟内,还可执行10个请求;reset值指的是配额将重置的Unix时间或新纪元时间(1970-01-01午夜后多少秒)。用完配额后,将收到一条简单的响应,由此知道已到达API极限。到达极限后必须等待配额重置

注意:很多API都要求你注册获得密钥后才能执行API调用。GitHub没有这样的要求,但获得API密钥后,配额将高得多。


使用 Pygal 可视化仓库

我们来进行可视化,呈现GitHub上Python项目的受欢迎程度。创建一个交互式条形图,条形的高度表示项目获得了多少颗星。单击条形将进入项目在GitHub上的主页

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
import requests
import pygal
from pygal.style import LightColorizedStyle as LCS, LightenStyle as LS

# 执行API调用并存储响应
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
r = requests.get(url)
print("Status code:", r.status_code)

# 将API响应存储在一个变量中
response_dict = r.json()
print("Total repositories:", response_dict['total_count'])

# 研究有关仓库的信息
repo_dicts = response_dict['items']

names, stars = [], []
for repo_dict in repo_dicts:
names.append(repo_dict['name'])
stars.append(repo_dict['stargazers_count'])

# 可视化
my_style = LS('#333366', base_style=LCS)
chart = pygal.Bar(style=my_style, x_label_rotation=45, show_legend=False)
chart.title = 'Most-Starred Python Projects on GitHub'
chart.x_labels = names

chart.add('', stars)
chart.render_to_file('python_repos.svg')
  • LS('#333366', base_style=LCS):使用LightenStyle(别名LS)定义了一种样式,并将基色设置为深蓝色;还传递了实参base_style,以使用LightColorizedStyle类(别名LCS)
  • show_legend=False隐藏了图例,因为只在图表中绘制一个数据系列
  • 由于不需要给这个数据系列添加标签,因此添加数据时,将标签设置成了空字符串

改进 Pygal 图表

我们将进行多个方面的定制,因此先来稍微调整代码结构,创建一个配置对象,其中包含要传递给Bar()的所有定制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--snip--

# 可视化
my_style = LS('#333366', base_style=LCS)

my_config = pygal.Config()
my_config.x_label_rotation = 45
my_config.show_legend = False
my_config.title_font_size = 24
my_config.label_font_size = 14
my_config.major_label_font_size = 18
my_config.truncate_label = 15
my_config.show_y_guides = False
my_config.width = 1000

chart = pygal.Bar(my_config, style=my_style)
chart.title = 'Most-Starred Python Projects on GitHub'
chart.x_labels = names

chart.add('', stars)
chart.render_to_file('python_repos.svg')
  • 9~11行设置了图表标题、副标签和主标签的字体大小。

    这个图表中副标签是x轴上的项目名和y轴上的大部分数字;主标签是y轴上为5000整数倍的刻度,这些标签更大,以与副标签区分开。

  • truncate_label=15将较长的项目缩短为15个字符(将鼠标指向被截短的项目名,将显示完整的项目名)

  • show_y_guides=False隐藏图表中的水平线

  • width=1000:自定义的宽度,让图表更充分地利用浏览器中的可用空间


添加自定义工具提示

在Python中,将鼠标指向条形将显示它表示的信息,这通常称为工具提示。下面来创建一个自定义工具提示,以同时显示项目的描述。

来看一个简单的示例,它可视化前三个项目,并给每个项目对应的条形都指定自定义标签。为此,我们向add()传递一个字典列表,而不是值列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pygal
from pygal.style import LightColorizedStyle as LCS, LightenStyle as LS

my_style = LS('#333366', base_style=LCS)
chart = pygal.Bar(style=my_style, x_label_rotation=45, show_legend=False)

chart.title = 'Python Projects'
chart.x_labels = ['httpie', 'django', 'flask']

plot_dicts = [
{'value': 16101'label': 'Description of httpie.'},
{'value': 15028, 'label': 'Description of django.'},
{'value': 14798, 'label': 'Description of flask.'},
]

chart.add('', plot_dicts)
chart.render_to_file('bar_descriptions.svg')
  • plot_dicts列表包含三个字典,'value'对应的值确定条形高度'label'对应的值给条形创建工具提示

根据数据绘图

为根据数据绘图,我们将自动生成plot_dicts。其中包含API调用返回的30个项目的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--snip--
# 研究有关仓库的信息
repo_dicts = response_dict['items']
print("Number of items:", len(repo_dicts))

names, plot_dicts = [], []
for repo_dict in repo_dicts:
names.append(repo_dict['name'])

plot_dict = {
'value': repo_dict['stargazers_count'],
'label': repo_dict['description'],
}
plot_dicts.append(plot_dict)

# 可视化
my_style = LS('#333366', base_style=LCS)
--snip--

chart.add('', plot_dicts)
chart.render_to_file('python_repos.svg')

在图表中添加可单击的链接

Pygal还允许将图表中的每个条形用作网站的链接。为此,在为每个项目创建的字典中,添加一个键为'xlink'的 键-值 对:

1
2
3
4
5
6
7
8
9
10
11
12
--snip--
names, plot_dicts = [], []
for repo_dict in repo_dicts:
names.append(repo_dict['name'])

plot_dict = {
'value': repo_dict['stargazers_count'],
'label': repo_dict['description'],
'xlink': repo_dict['html_url'],
}
plot_dicts.append(plot_dict)
--snip--

单击图表中的任何条形,都将在浏览器中打开一个新的标签页,并在其中显示相应项目的GitHub页面。


Hacker News API

为探索如何使用其他网站的API调用,我们来看看

Hacker News网站,用户分享编程和技术方面的文章,并就这些文章展开积极的讨论。Hacker News的API让你能够访问有关该网站所有文章和评论的信息,且不要求通过注册获得密钥。

已被墙。


问题

Jupyter Notebook 用matplotlib作图显示中文乱码

1
plt.rc('font', family='SimHei', size=7)  ##显示中文,字体大小根据需要调整

存在极端值干扰

  • 使用query()做范围的筛选

  • 根据切比雪夫定理做筛选:

    • 所有数据中,至少有75%的数据位于平均数2个标准差范围内
  • 所有数据中,至少有88.9%的数据位于平均数3个标准差范围内

    • 所有数据中,至少有96%的数据位于平均数5个标准差范围内

条形图和直方图的区别

  • 条形图(bar chart)

    1. 条形的高度表示各类别频数的多少,其宽度(表示类别)则是固定的;
    2. 条形图各矩形分开排列(有间隙)
    3. 主要用于展示分类数据
  • 直方图(Histogram)

    1. 面积表示各组频数的多少,矩形的高度表示每一组的频数或频率宽度则表示各组的组距
    2. 分组数据具有连续性,直方图各矩形通常是连续排列
    3. 主要用于展示数值型数据

绘图时x轴/y轴的刻度标签重叠

Matplotlib绘图时x轴标签重叠的解决办法 - 云+社区 - 腾讯云

  • 方法一:

    拉长画布

  • 方法二:

    调整刻度标签的字体大小:plt.tick_params(axis='x', labelsize=8)

  • 方法三:

    将纵向图改为横向,如条形图:plt.barh()

  • 方法四

    旋转刻度标签的角度plt.xticks(rotation=30)


Pandas常用图表简介

箱线图(箱型图/盒型图)

直方图用于显示一组数据的分布情况,而箱线图用于显示一组数据内部的分散情况。例如: 最大值、最小值,中位数,4分位数、异常值等等。

  • 箱线图各部位的含义

    箱线图各部位的含义

    • 上四分位数(QU):第75%位数

    • 下四分位数(QL):第25%位数

    • 异常值的标准:

      • IQR(Inter-Quatile range):四分位距

        IQR = QU - QLIQR包含了全部观察值的一半

      • 过小的异常值为 $<$ QL-1.5IQR的值

      • 过大的异常值 $>$ QU+1.5IQR的值

    Matplotlib中绘制箱线图的函数为DataFrame.boxplot()


一文学会matplotlib绘图

快捷键

快捷键 功能
Ctrl + R 运行查询语句
Ctrl + F 查找,并有替换选项可勾选
0%