0%

前提条件

  • 安装Docker 1.13或更高的版本
  • 按照Part3部分,获取Docker Compos
  • 获取预装Docker for MacDocker for WindowsDocker Machine,但在Linux系统上需要直接安装它。在没有Hyper-V的Windows 10系统之前以及Windows 10 Home中,使用Docker Toolbox
  • 阅读Part1。
  • 学习Part2中的如何创建容器。
  • 确保您已发布了那个推送到仓库的friendlyhello镜像。我们在这里使用该共享镜像。
  • 确定你的镜像作为一个已部署的容器。运行下面这条命令,插入你的usernamerepo、和tag:docker run -p 80:80 username/repo:tag,然后访问http://localhost/
  • 有一份Part3中的docker-compose.yml

介绍

在Part3中,你介绍了在Part2中编写的应用程序,并通过将其转化为service来定义应该如何在生产环境中运行,并在其进程内扩大5倍。

在Part4部分,您将此应用程序部署到集群上,并在多台机器上运行它。通过将多台机器连接到称为swarm的“Dockerized”群集,使多容器,多机器应用成为可能。

了解Swarm集群

Swarm是一组运行Docker并加入到集群中的机器。发生这种情况后,您将继续运行您习惯的Docker命令,但现在它们将由swarm manager在群集上执行。swarm中的机器可以是物理的或虚拟的。加入swarm后,他们被称为节点。

swarm管理器可以使用几种策略来运行容器,例如“emptiest node”—它用容器填充最少使用的机器。或者“global”,它可以确保每台机器只获取指定容器的一个实例。您指示swarm manager在Compose文件中使用这些策略,就像您已经使用的策略一样。

swarm manager是群体中唯一可以执行你的命令,或者授权其他机器作为worker加入群体的机器。worker只是提供能力,并没有权力告诉任何其他机器它能做什么和不能做什么。

到目前为止,您已经在本地机器上以单主机模式使用Docker。但是Docker也可以切换到swarm模式,这就是使用群集的原因。立即启用swarm模式使当前的机器成为swarm manager。从此,Docker将运行您在您管理的swarm上执行的命令,而不仅仅是在当前机器上执行。

设置你的swarm

一个swarm是由多个节点组成,这些节点可以是物理机或虚拟机。基本概念很简单:运行docker swarm init来启用swarm模式,并使您当前的机器成为swarm管理器,然后在其他机器上运行docker swarm join,使其他机器以worker的身份加入到swarm中。我们将使用虚拟机快速创建一个双机群集,并将其变成swarm。

创建一个集群

本地虚拟机(Mac,Linux,Windows 7和8)

您需要一个可以创建虚拟机(VM)的虚拟机管理程序,因此请为您的计算机的操作系统安装Oracle VirtualBox

注意:如果你在Windows系统下,并且已经安装了Hyper-V,例如Windows 10,那就没必要安装VirtualBox了,你可以使用Hyper-V替代。如果你正在使用Docker Toolbox,你应该已经安装好了VirtualBox。

现在,使用docker-machine创建两个虚拟机VM,使用VirtualBox驱动:

1
2
docker-machine create --driver virtualbox myvm1
docker-machine create --driver virtualbox myvm2

本地虚拟机(Windows 10/Hyper-V)

首先,快速为您的虚拟机(VM)创建一个虚拟交换机以便共享,以便它们可以相互连接。

  • 1.开启 Hyper-V Manager
  • 2.点击右上角菜单中的Virtual Switch Manager
  • 3.单击创建类型为External虚拟交换机
  • 4.将它命名为myswitch,然后选中复选框以共享主机的活动网络适配器

现在,使用我们的节点管理工具docker-machine创建几个虚拟机:

1
2
docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1
docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm2

列出虚拟机,并显示其IP地址

你现在创建了两个虚拟机,名叫myvm1myvm2

使用下面的命令来列出这些虚拟机以及他们的IP地址。

1
docker-machine ls

这里是这个命令的输出示例。

1
2
3
4
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Running tcp://192.168.99.100:2376 v17.06.2-ce
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v17.06.2-ce

初始化swarm和节点

第一台机器作为manager,它负责执行管理命令并认证worker机器加入集群,第二台机器是worker。

你可以对你的虚拟机通过docker-machine ssh来发送命令。通过执行docker swarm init来指导myvm1来成为swarm manager,然后你会看到像下面这样的输出:

1
2
3
4
5
6
7
8
9
10
$ docker-machine ssh myvm1 "docker swarm init --advertise-addr <myvm1 ip>"
Swarm initialized: current node <node ID> is now a manager.

To add a worker to this swarm, run the following command:

docker swarm join \
--token <token> \
<myvm ip>:<port>

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

端口2377和2376

始终使用端口2377(群管理端口)运行docker swarm initdocker swarm join,或根本不指定运行端口,并让其采用默认值。

docker-machine ls返回的计算机IP地址包括端口2376,它是Docker守护进程端口。请勿使用此端口,否则可能会遇到错误

无法使用SSH?试试—native-ssh标志

如果由于某些原因,您无法将命令发送给Swarm管理器,Docker Machine可以选择让您使用自己的系统的SSH。只需在调用ssh命令时指定--native-ssh标志:

1
docker-machine --native-ssh ssh myvm1 ...

如您所见,对docker swarm init的响应包含一个预配置的docker swarm join命令,您可以在要添加的任何节点上运行该命令。复制这条命令,并通过docker-machine ssh发送到myvm2,使myvm2作为woker的角色来加入到你新创建的集群中。

1
2
3
4
5
$ docker-machine ssh myvm2 "docker swarm join \
--token <token> \
<ip>:2377"

This node joined a swarm as a worker.

恭喜,您已经创建了您的第一个swarm集群!

在manager机器上运行docker node ls来查看swarm中的node:

1
2
3
4
$ docker-machine ssh myvm1 "docker node ls"
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
brtu9urxwfd5j0zrmkubhpkbd myvm2 Ready Active
rihwohkh3ph38fhillhhb84sk * myvm1 Ready Active Leader

离开一个swarm

如果你的某个节点想要退出集群,你可以在节点上运行docker swarm leave

发布你的应用到swarm集群

最困难的部分已经结束了。现在你只需重复第3部分用于部署新swarm的流程即可。请记住,只有像myvm1这样的群集管理器才能执行Docker命令;worker机器只是提供使用而已。

为swarm manager配置docker-machine shell

到目前为止,你已经可以在docker-machine ssh中包裹Docker命令来在虚拟机上执行指令了。另一种选择是运行docker-machine env <machine>来获取并执行一个命令,该命令将当前shell配置为与虚拟机上的Docker守护进程进行通信。此方法对下一步更有利,因为它允许您使用本地docker-compose.yml文件“远程”部署应用程序,而无需将其复制到任何位置。

键入docker-machine env myvm1,然后复制粘贴并运行作为输出最后一行提供的命令,这样可以将shell配置为swarm manager可以与myvm1进行对话。

配置shell的命令根据你是Mac,Linux还是Windows而有所不同。

Mac,Linux

Mac或Linux上的Docker Machine shell

运行docker-machine env myvm1来获取与myvm1进行交互的命令:

1
2
3
4
5
6
7
$ docker-machine env myvm1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/sam/.docker/machine/machines/myvm1"
export DOCKER_MACHINE_NAME="myvm1"
# Run this command to configure your shell:
# eval $(docker-machine env myvm1)

运行给出的命令,来配置你的shell来与myvm1进行交互:

1
eval $(docker-machine env myvm1)

运行docker-machine ls以验证myvm1处于激活状态,星号表示激活状态。

1
2
3
4
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 * virtualbox Running tcp://192.168.99.100:2376 v17.06.2-ce
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v17.06.2-ce

Windows

运行docker-machine env myvm1来获取与myvm1进行交互的命令:

1
2
3
4
5
6
7
8
PS C:\Users\sam\sandbox\get-started> docker-machine env myvm1
$Env:DOCKER_TLS_VERIFY = "1"
$Env:DOCKER_HOST = "tcp://192.168.203.207:2376"
$Env:DOCKER_CERT_PATH = "C:\Users\sam\.docker\machine\machines\myvm1"
$Env:DOCKER_MACHINE_NAME = "myvm1"
$Env:COMPOSE_CONVERT_WINDOWS_PATHS = "true"
# Run this command to configure your shell:
# & "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression

运行给出的命令,来配置你的shell来与myvm1进行交互:

1
& "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression

运行docker-machine ls以验证myvm1处于激活状态,星号表示激活状态。

1
2
3
4
PS C:PATH> docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 * hyperv Running tcp://192.168.203.207:2376 v17.06.2-ce
myvm2 - hyperv Running tcp://192.168.200.181:2376 v17.06.2-ce

在swarm manager上发布应用

现在你已经拥有了myvm1,你可以使用它的权力作为swarm manager来部署你的应用,方法是使用第三部分中的docker stack deploy命令将你的本地副本docker-compose.yml发布到myvm1。这个命令也许会花费几秒钟时间来完成这一操作,部署需要花一段时间才能完成。在swarm manager上使用docker service ps <service_name>命令验证所有服务是否已被重新部署。

您通过docker-machine shell配置链接到myvm1,并且您仍然可以访问本地主机上的文件。确保你和之前在同一个目录下,并且其中包括你在第3部分中创建的docker-compose.yml文件。

就像之前一样,运行以下命令在myvm1上部署应用程序。

1
docker stack deploy -c docker-compose.yml getstartedlab

就是这样,该应用程序就成功部署在了swarm集群上了!

注意:如果你的镜像保存在了一个私有仓库而不是Docker Hub上,你需要登录到通过命令docker login <your-registry>来登录到这个仓库,并且然后你需要在上面的命令添加--with-registry-auth指令。例如:

1
2
3
docker login registry.example.com

docker stack deploy --with-registry-auth -c docker-compose.yml getstartedlab

这使用加密的WAL日志将登录令牌从本地客户端传递到部署服务的群集节点。有了这些信息,这些节点就能够登录到仓库并提取镜像。

现在你可以使用Part3中的docker命令。只有这次注意到services(和相关容器)已经在myvm1myvm2之间分配了。

1
2
3
4
5
6
7
8
$ docker stack ps getstartedlab

ID NAME IMAGE NODE DESIRED STATE
jq2g3qp8nzwx getstartedlab_web.1 john/get-started:part2 myvm1 Running
88wgshobzoxl getstartedlab_web.2 john/get-started:part2 myvm2 Running
vbb1qbkb0o2z getstartedlab_web.3 john/get-started:part2 myvm2 Running
ghii74p9budx getstartedlab_web.4 john/get-started:part2 myvm1 Running
0prmarhavs87 getstartedlab_web.5 john/get-started:part2 myvm2 Running

使用docker-machine envdocker-machine ssh连接到VM

  • 要将shell设置为与myvm2等其他机器通信,只需在相同或不同的shell中重新运行docker-machine env,然后运行给定的命令以指向myvm2。这里指的是当前的shell。如果你更改为未配置的shell或打开一个新的shell,则需要重新运行这些命令。使用docker-machine ls列出机器列表,查看它们所处的状态,获取IP地址,如果有的话,并找出具体连接到的是哪一个地址。更多请参阅Docker Machine getting started topics

  • 或者,你可以以docker-machine ssh <machine> "<command>"的形式来打包Docker命令,该命令可以直接登陆到VM,但不会立即访问本地主机上的文件。

  • 在Mac和Linux上,你可以使用docker-machine scp <file> <machine>:~来在机器间复制文件,但在Windows上,需要使用一个Linux类似Git Bash的终端模拟器来完成这类工作。

本教程演示了docker-machine sshdocker-machine env因为这些都可以通过docker-machineCLI在平台上使用。

访问你的集群

你可以从myvm1myvm2的IP地址访问您的应用程序。

你创建的网络在它们之间共享负载均衡。运行docker-machine ls来获取你的VM的IP地址,并通过浏览器访问其中的任何一个,点击刷新(或者仅仅使用curl来访问)

有五个可能的容器ID全部随机循环,体现了负载均衡。

两个IP地址工作的原因是群中的节点参与入口路由网格。这可以确保部署在集群中某个端口的服务始终将该端口保留给自己,而不管实际运行容器的节点是什么。以下是三节点swarm的端口8080上发布的名为my-web的服务的路由网络示意图:

连接有问题?

请记住,要使用swarm中的入口网络,在启用swarm模式之前,需要在swarm节点之间打开以下端口:

  • 端口7946 TCP/UDP (用于容器网络发现)
  • 端口4789 UDP (用于容器入口网络)

迭代和缩放你的应用程序

在这里,你可以完成你在Part2和Part3中学到的一切。

通过修改docker-compose.yml文件,可以缩放应用程序。

通过编辑代码,来改变应用的行为,然后重新构建,并将新的镜像push上去。(要做到这一点,请按照之前用于构建应用程序和发布镜像的相同步骤)。

无论是哪种情况,只需要通过再次运行docker stack deploy就可以发布这些变更。

你可以加入任何虚拟的或物理的机器到这个swarm中,对myvm2使用相同的docker swarm join命令,然后集群的容量就被扩大了。在运行docker stack deploy之后,你的应用程序就可以利用到这些资源了。

清空并重启

堆栈和swarm

你可以通过运行docker stack rm来卸下堆栈。例如:

1
docker stack rm getstartedlab

保留或删除swarm

在稍后,如果您想要使某个worker离开swarm,可以在worker上使用docker-machine ssh myvm2 "docker swarm leave"来卸下woker,如果是manager的话,可以这样执行docker-machine ssh myvm1 "docker swarm leave --force",但在后面的Part5的教学中,你还需要它,所以暂时保留。

重置docker-machine shell变量设置

你可以通过你当前的shell执行以下命令来重置docker-machine环境变量。

在Mac或Linux上,命令如下:

1
eval $(docker-machine env -u)

在Windows上,命令如下:

1
& "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env -u | Invoke-Expression

这将shell与docker-machine创建的虚拟机断开连接,并允许你继续在同一个shell中工作,现在使用本机docker命令。更多信息,请见Machine topic on unsetting environment variables

重启Docker machines

如果你关闭本地主机,Docker machines将停止运行。你可以通过运行docker-machine ls来检查机器运行的状态。

1
2
3
4
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Stopped Unknown
myvm2 - virtualbox Stopped Unknown

重启已经停止的机器,运行:

1
docker-machine start <machine-name>

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ docker-machine start myvm1
Starting "myvm1"...
(myvm1) Check network to re-create if needed...
(myvm1) Waiting for an IP...
Machine "myvm1" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.

$ docker-machine start myvm2
Starting "myvm2"...
(myvm2) Check network to re-create if needed...
(myvm2) Waiting for an IP...
Machine "myvm2" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.

内容回顾

在part4部分,你学习到了什么是swarm,节点在swarm中可以作为worker,也可以作为manager,创建一个swarm,并在上面发布一个应用。你看到Docker的核心命令和part3中并没有什么不同,他们只需要将目标锁定在swarm主机上运行。你还看到了Docker网络的力量,即使它们运行在不同的机器上,也可以跨容器保持请求负载均衡。最后,你学习了如何在集群上迭代和缩放应用程序。以下是您可能想要运行的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux)
docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10
docker-machine env myvm1 # View basic information about your node
docker-machine ssh myvm1 "docker node ls" # List the nodes in your swarm
docker-machine ssh myvm1 "docker node inspect <node ID>" # Inspect a node
docker-machine ssh myvm1 "docker swarm join-token -q worker" # View join token
docker-machine ssh myvm1 # Open an SSH session with the VM; type "exit" to end
docker node ls # View nodes in swarm (while logged on to manager)
docker-machine ssh myvm2 "docker swarm leave" # Make the worker leave the swarm
docker-machine ssh myvm1 "docker swarm leave -f" # Make master leave, kill swarm
docker-machine ls # list VMs, asterisk shows which VM this shell is talking to
docker-machine start myvm1 # Start a VM that is currently not running
docker-machine env myvm1 # show environment variables and command for myvm1
eval $(docker-machine env myvm1) # Mac command to connect shell to myvm1
& "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression # Windows command to connect shell to myvm1
docker stack deploy -c <file> <app> # Deploy an app; command shell must be set to talk to manager (myvm1), uses local Compose file
docker-machine scp docker-compose.yml myvm1:~ # Copy file to node's home dir (only required if you use ssh to connect to manager and deploy the app)
docker-machine ssh myvm1 "docker stack deploy -c <file> <app>" # Deploy an app using ssh (you must have first copied the Compose file to myvm1)
eval $(docker-machine env -u) # Disconnect shell from VMs, use native docker
docker-machine stop $(docker-machine ls -q) # Stop all running VMs
docker-machine rm $(docker-machine ls -q) # Delete all VMs and their disk images

前提条件

  • 安装Docker 1.13或更高的版本
  • 获取Docker Compose。在Docker for MacDocker for Windows中,它已预先安装,这一步很方便。在Linux系统上,您需要直接安装它。在没有Hyper-V的Windows 10系统上,使用Docker Toolbox
  • 阅读Part1部分。
  • 学习Part2中如何创建容器。
  • 确定你已经通过上传到仓库的方式发布了friendlyhello镜像。我们在这里使用共享镜像。
  • 确定你的镜像作为一个已部署的容器。运行下面这条命令,插入你的usernamerepo、和tag:docker run -p 80:80 username/repo:tag,然后访问http://localhost/

介绍

在第三部分,我们将扩展我们的应用并实现负载均衡。为了做到这一点,我们必须在分布式应用程序的层次结构中升一级:service(服务)

  • Stack
  • Services(服务 你在这里)
  • Container(参见 part2)

关于Services

在一个分布式应用中,应用的不同部分被称为”services”。例如,想象你有一个影片分享站点,它也许包括一个关于将应用数据存储在数据库中的service,一个在用户上传一些东西之后在后台对其进行转码操作的service,一个前端的服务,等等。

Services实际上只是“生产中的容器”。service只运行一个镜像,但用于编纂镜像的运行方式—应该使用哪个端口,应该运行多少个容器副本以便服务具有所需的容量,等等。缩放service会更改运行该软件的容器实例的数量,从而为流程中的服务分配更多计算资源。

幸运的是,使用Docker平台定义,运行和扩展services非常简单 — 仅仅写一个docker-compose.yml文件即可。

你的第一个docker-compose.yml文件

一个docker-compose.yml文件是一个用于定义Docker容器在生产环境中的行为的YAML文件。

docker-compose.yml

将此文件另存为docker-compose.yml,无论你在哪里。确保您已将第2部分中创建的镜像推送到注册表中,并通根据你的镜像的具体的信息替换掉username/repo:tag部分来更新此.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: "3"
services:
web:
# replace username/repo:tag with your name and image details
image: username/repo:tag
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "80:80"
networks:
- webnet
networks:
webnet:

这个docker-compose.yml文件告诉Docker要做以下事情:

  • 按照Part2中的方式从仓库中下拉我们的镜像。
  • 运行镜像的5个实例作为一个名为web的service,限制每个实例,最多占用10%CPU(所有的核)、50MB的RAM。
  • 如果一个容器失败了立刻重启。
  • 将主机上的端口80映射到web的端口80。
  • 指示web容器通过名为webnet负载均衡网络共享80端口。(在内部,容器本身在临时端口上发布到web的80端口)。
  • 使用默认设置(这是一个负载均衡覆盖网络)定义webnet网络。

运行你的全新的负载均衡应用

在我们可以使用docker stack deploy命令之前,我们首先运行:

1
docker swarm init

注意:我们在Part4部分深入了解该命令的含义。如果你不运行docker swarm init命令的话,你会得到这样一条错误信息:”this node is not a swarm manager.”。

现在,让我们来运行它吧。你需要给你的应用起一个名字。这里我们起名为getstartedlab

1
docker stack deploy -c docker-compose.yml getstartedlab

我们的单个service堆栈在一台主机上运行了5个部署映像的容器实例。让我们来进一步了解。

在我们的应用程序中获取一项service的service ID:

1
docker service ls

查找webservice的输出信息,找到那个以你的应用程序名称作为前缀信息。如果您将其命名为与此示例中显示的相同,那么这里的名称为getstartedlab_web。还列出了service ID以及副本数量,镜像名称和对外暴露的端口。

在service中运行的单个容器称为task(任务)

1
docker service ps getstartedlab_web

如果您只列出系统中的所有容器,任务也会显示出来,它不会被service所过滤:

1
docker container ls -q

你可以运行几次curl -4 http://localhost,或者通过浏览器打开这个链接尝试刷新几次。

无论哪种方式,容器ID都会发生变化,从而显示负载均衡;在每个请求中,以循环方式选择5个任务中的一个来响应。容器ID与前一个命令(docker container ls -q)的输出相匹配。

在Windows10下运行?

Windows10的PowerShell可以使用curl命令,但如果不行的话,你可以尝试获取一个Linux终端模拟器,例如Git BASH或者下载很相似的wget for Windows

响应时间慢?

根据您的环境的网络配置,容器可能需要长达30秒才能响应HTTP请求。这并不代表Docker或群集性能,而是我们稍后在本教程中讨论的未满足的Redis依赖项。就目前而言,访客柜台并不是出于同样的原因;我们还没有添加service来保存数据。

扩展应用程序

你可以通过修改docker-compose.ymlreplicas的值来扩展应用,保存docker-compose.yml的改变之后,重新运行docker stack deploy命令:

1
docker stack deploy -c docker-compose.yml getstartedlab

Docker执行一个就地更新,不需要先撤下堆栈或杀死任何容器。

现在,重新运行docker container ls -q以查看重新配置的已部署实例。如果您扩大副本,则会启动更多任务,因此还会启动更多容器。

撤下应用和swarm

  • 通过指令docker stack rm来撤下应用:
1
docker stack rm getstartedlab
  • 撤下swarm
1
docker swarm leave --force

用Docker新建并扩展您的应用程序非常简单。您已经朝着学习如何在生产中运行容器迈出了一大步。接下来,您将学习如何将这个应用程序作为Docker机器群集上的真正群体运行。

注意:像这里的Compose文件是用于通过Docker来定义应用程序,并且可以通过Docker Cloud上传到云端,或者任何带有Docker 企业版的云服务上。

回顾

总而言之,在输入docker run运行时非常简单,生产中的容器真正实现将其作为服务运行。服务在Compose文件中编写容器的行为,此文件可用于缩放,限制和重新部署我们的应用程序。对服务的更改可以在运行时适用,使用启动服务的相同命令:docker stack deploy

现阶段需要学习的一些命令:

1
2
3
4
5
6
7
8
docker stack ls                                            # List stacks or apps
docker stack deploy -c <composefile> <appname> # Run the specified Compose file
docker service ls # List running services associated with an app
docker service ps <service> # List tasks associated with an app
docker inspect <task or container> # Inspect task or container
docker container ls -q # List container IDs
docker stack rm <appname> # Tear down an application
docker swarm leave --force # Take down a single node swarm from the manager

前提条件

1
docker run hello-world

介绍

现在是开始以Docker方式构建应用程序的时候了。我们从这种应用程序的层次结构的容器的底部开始,进行详细介绍。在这个层次上面是一个服务,它定义了容器在生产中的行为方式,如Part3部分所述。最后,在顶层的堆栈部分定义了Part5部分中涵盖的所有服务的交互。

  • Stack (堆栈)
  • Services (服务)
  • Container(容器,你在这里)

您的新开发环境

之前,如果您要开始编写Python应用程序,您第一个要做的事情就是在您的机器上安装Python运行环境。但是,这会造成您的计算机上的环境需要完美适合您的应用程序按预期运行,并且还需要与您的生产环境相匹配。

使用Docker,您可以将一个可移植的Python运行环境作为一个镜像获取,无需安装。然后,您的构建可以将基础Python镜像与应用程序代码一起包括在内,确保您的应用程序,依赖项和运行环境都在一起。

这些可移植的镜像是由称为Dockerfile的东西定义的。

通过Dockerfile定义一个容器

Dockerfile定义了容器内的环境发生的事情。像访问网络接口以及磁盘驱动器等资源是在此环境内虚拟化的,这与系统的其余部分是隔离开的,因此您需要将端口映射到外部世界,并明确指定要将哪些文件“复制”到该环境中。但是,在完成这些之后,您可以预期,在此Dockerfile中定义的应用程序构建在运行时的行为完全相同。

Dockerfile

创建一个空目录。改变目录(cd)到这个新的目录下,创建一个叫做Dockerfile的文件,将下面的内容复制粘贴到这个文件中,并保存。注意记下Dockerfile中的注释部分。

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
# Use an official Python runtime as a parent image
# 使用一个官方的Python运行时环境作为父镜像
FROM python:2.7-slim

# Set the working directory to /app
# 设置工作目录到/app目录下
WORKDIR /app

# Copy the current directory contents into the container at /app
# 复制当前目录内容到容器的/app目录下
ADD . /app

# Install any needed packages specified in requirements.txt
# 安装所有requirements.txt中指定的需要的包
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
# 使得该容器的80端口对外可用
EXPOSE 80

# Define environment variable
# 定义环境变量
ENV NAME World

# Run app.py when the container launches
# 当容器加载时,运行app.py
CMD ["python", "app.py"]

这个Dockerfile提到了我们尚未创建的两个文件,名叫app.pyrequirements.txt。接下来让我们来创建出来。

应用程序本身

创建两个文件requirements.txtapp.py,并把他们放在和Dockerfile相同的目录下。这就完成了我们的应用程序,正如你看到的一样,非常简单。当上面的Dockerfile被内置到镜像中时,由于DockerfileADD命令,app.pyrequirements.txt存在,并且app.py的输出可以通过HTTP访问,这要归功于EXPOSE命令。

requirements.txt

1
2
Flask
Redis

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from flask import Flask
from redis import Redis, RedisError
import os
import socket

# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)

app = Flask(__name__)

@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"

html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>" \
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)

if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)

现在我们看到pip install -r requirements.txt命令安装了Python的Flask和Redis库,并且应用打印出了环境变量NAME,同时输出了方法socket.gethostname()输出的内容。最后,因为Redis没有运行(因为我们只安装了Python库,而不是Redis本身),所以这里应该会失败,并产生错误消息。

注意:在容器内部访问主机的名称将检索容器ID,这与正在运行的可执行文件的进程ID相似。

就是这样!你的系统上不需要Python或者任何requirements.txt文件,也不需要在你的系统上安装或运行此镜像。这看起来你并没有真正用Python和Flask建立一个运行环境,但事实上已经建立好了。

构建一个应用

我们准备构建应用程序。确保您仍然处于新目录的顶层。以下是ls命令应该显示的内容:

1
2
$ ls
Dockerfile app.py requirements.txt

现在运行build命令。这会创建一个Docker镜像,我们将使用-t标记它,这样可以给它指定一个友好的名称。

1
docker build -t friendlyhello .

你构建的镜像在哪里?它在你的机器的本地Docker镜像注册表中:

1
2
3
4
$ docker image ls

REPOSITORY TAG IMAGE ID
friendlyhello latest

Linux用户的故障排除

DNS设置

代理服务器在启动并运行后可以阻止与您的网络应用程序的连接。如果您位于代理服务器的后面,请使用ENV命令为您的代理服务器指定主机和端口,将以下行添加到Dockerfile中:

1
2
3
# Set proxy server, replace host:port with values for your servers
ENV http_proxy host:port
ENV https_proxy host:port

代理服务设置

DNS错误配置可能会导致pip出现问题。您需要设置您自己的DNS服务器地址以使pip正常工作。您可能需要更改Docker守护程序的DNS设置。您可以按照以下方式来编辑(或者创建)带有dns信息的配置文件/etc/docker/daemon.json

1
2
3
{
"dns": ["your_dns_address", "8.8.8.8"]
}

在上面的例子中,列表的第一个元素是你的DNS服务器的地址。第二项是Google的DNS,当第一项不可用时可以使用它。

在继续之前,保存`daemon.json并重新启动docker服务。

sudo service docker restart

修复后,重试运行build命令。

运行app

运行应用程序,使用-p将机器的端口4000映射到容器的已发布端口80

1
docker run -p 4000:80 friendlyhello

你可以在http://0.0.0.0:80看到一条消息,Python正在为你的应用程序提供服务。但是,该消息来自容器内部,它不知道您将该容器的端口80映射到4000,从而制作正确的URLhttp://localhost:4000

在网络浏览器中转到该URL以查看网页上显示的显示内容。

注意:如果你正在在Windows7上使用Docker Toolbox,使用Docker Machine IP来替代localhost。例如:http://192.168.99.100:4000/。要查找IP地址,请使用该命令`docker-machine ip`。

您也可以在shell中使用curl命令来查看相同的内容。

1
2
3
$ curl http://localhost:4000

<h3>Hello World!</h3><b>Hostname:</b> 8fc990912a14<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>

这个4000:80的端口重映射是为了演示Dockerfile中的EXPOSE与使用docker run -p发布的内容之间的区别。在后面的步骤中,我们只需将主机上的端口80映射到容器中的端口80并使用http://localhost

在终端中点击CTRL + C退出。

在Windows下,显式的停止容器

在Windows系统下,CTRL+C不会停止容器。到目前为止,首先键入CTRL+C以获取提示(或打开另一个shell),然后输入docker container ls来列出运行中的容器,其次是docker container stop <Container NAME or ID>来停止容器。否则,你会在下一步重新运行容器时得到一个来自守护进程的错误消息。

现在让我们以分离模式在后台运行应用程序:

1
docker run -d -p 4000:80 friendlyhello

您可以获取应用的长容器ID,然后将其显示到终端。你的容器在后台运行。你也可以通过docker container ls来看到简短的容器ID(并且在运行命令时可以互换使用)。

注意到CONTAINER IDhttp://localhost:4000上的内容匹配。

现在我们使用docker container stop来结束指定CONTAINER ID
的进程,像这样:

1
docker container stop 1fa4ab2cf395

分享你的image

为了演示我们刚才创建的可移植性,我们上传我们构建的镜像并在其他地方运行它。毕竟,当您想要将容器部署到生产环境时,您需要知道如何推送注册表。

注册表是存储库的集合,而存储库是镜像的集合 - 有点像GitHub存储库,但不同的是这里代码已经创建。注册表上的帐户可以创建许多存储库。 docker CLI默认使用Docker的公共注册表。

注意:我们在这里使用Docker的公共注册表仅仅是因为它是免费和预先配置的,但是有许多公共选项可供选择,您甚至可以使用Docker Trusted Registry设置您自己的私有注册表。

登录你的Docker ID

如果你没有Docker账号,需要在cloud.docker.com上注册一个。记下你的用户名。

登录到本地计算机上的Docker公共注册表。

1
$ docker login

给镜像打标签

将本地镜像与注册表中的存储库相关联的符号是username/repository:tag。该tag是可选的,但建议使用,因为它是注册管理机构用于为Docker镜像提供版本的机制。为该上下文提供存储库并标记有意义的名称,例如get-started:part2。这将镜像置于启动存储库中,并将其标记为part2

现在,给镜像整体的打上标签。带入你的用户名、仓库名、和标签名运行docker tag image以便将镜像上传到你想要的地址。命令的语法如下:

1
docker tag image username/repository:tag

例如:

1
docker tag friendlyhello john/get-started:part2

运行docker image ls来查看你的新的带有标签信息的镜像:

1
2
3
4
5
6
7
$ docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
friendlyhello latest d9e555c53008 3 minutes ago 195MB
john/get-started part2 d9e555c53008 3 minutes ago 195MB
python 2.7-slim 1c7128a655f6 5 days ago 183MB
...

发布镜像

上传带有标签信息的镜像到仓库中:

1
docker push username/repository:tag

完成后,此上传的结果将公开发布。如果你登录到Docker Hub,你会在那里看到带有pull命令的新的镜像文件。

从远程仓库中下拉并且运行镜像

截止到目前,你可以使用docker run命令来在任何机器上运行你的应用:

1
docker run -p 4000:80 username/repository:tag

如果镜像在本地机器上不可用,Docker会从远程仓库中下拉到本地。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ docker run -p 4000:80 john/get-started:part2
Unable to find image 'john/get-started:part2' locally
part2: Pulling from john/get-started
10a267c67f42: Already exists
f68a39a6a5e4: Already exists
9beaffc0cf19: Already exists
3c1fe835fb6b: Already exists
4c9f1fa8fcb8: Already exists
ee7d8f576a14: Already exists
fbccdcced46e: Already exists
Digest: sha256:0601c866aab2adcc6498200efd0f754037e909e5fd42069adeff72d1e2439068
Status: Downloaded newer image for john/get-started:part2
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

不管docker run在哪里运行,它都会将你的镜像以及Python和requirements.txt中所有的依赖关系一起提取出来,并运行您的代码。它们都在一个整洁的小包中一起旅行,并且Docker不需要您在主机上安装任何东西来运行它。

Part2结论

这里列出了这个页面的基本Docker命令,以及一些相关的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
docker build -t friendlyhello .  # Create image using this directory's Dockerfile
docker run -p 4000:80 friendlyhello # Run "friendlyname" mapping port 4000 to 80
docker run -d -p 4000:80 friendlyhello # Same thing, but in detached mode
docker container ls # List all running containers
docker container ls -a # List all containers, even those not running
docker container stop <hash> # Gracefully stop the specified container
docker container kill <hash> # Force shutdown of the specified container
docker container rm <hash> # Remove specified container from this machine
docker container rm $(docker container ls -a -q) # Remove all containers
docker image ls -a # List all images on this machine
docker image rm <image id> # Remove specified image from this machine
docker image rm $(docker image ls -a -q) # Remove all images from this machine
docker login # Log in this CLI session using your Docker credentials
docker tag <image> username/repository:tag # Tag <image> for upload to registry
docker push username/repository:tag # Upload tagged image to registry
docker run username/repository:tag # Run image from a registry

翻译自docker官方文档

欢迎!我们很高兴您来学习Docker。 Docker入门教程将教你如何:

  • 1.设置您的Docker环境(本节内容)
  • 2.构建一个镜像,并将其作为一个容器运行
  • 3.通过运行多个容器来缩放你的应用
  • 4.通过集群来分发你的应用
  • 5.通过添加后端数据库来堆叠服务
  • 6.发布你的应用到生产环境

Docker的概念

Docker是开发人员和系统管理员使用容器开发部署运行应用程序的平台。使用Linux容器来部署应用程序称为集装箱化(Containerization)。容器的概念并不新,但它们用于部署应用程序会很轻松。

集装箱化越来越受欢迎,因为集装箱是:

  • 灵活:即使是最复杂的应用也可以被集装箱化。
  • 轻量级:容器利用并共享主机内核。
  • 通用:您可以即时部署更新和升级。
  • 轻便:你可以在本地构建,发布到云端,并且在任何地方运行。
  • 可扩展:您可以增加和自动分发容器副本。
  • 可堆叠:您可以及时的纵向堆叠服务。

镜像和容器

一个容器通过运行一个镜像来启动。一个镜像是一个可执行的程序包,其中包含运行该程序所需的所有内容,包括代码,运行时,库,环境变量和配置文件。

容器是镜像的运行时实例 - 即执行时将镜像加载到内存中,这个内存中的部分就是容器(即具有状态或用户进程的镜像)。Linux环境下,你可以通过命令docker ps来查看你当前正在运行的容器。

容器和虚拟机

一个容器在Linux的本地运行,并与其他容器共享主机的内核。它运行在一个独立的进程,不占用任何其他可执行文件的内存,从而达到轻量化。

对比之下,虚拟机(VM)运行一个完整的“guest”操作系统,通过虚拟机管理程序虚拟访问主机资源。一般来说,虚拟机提供的环境比大多数应用程序需要的资源更多。

准备你的Docker环境

在支持的平台上安装Docker社区版本(CE)或者企业版(EE)。

对于完整的Kubernetes集成

  • Docker for Mac上的Kubernetes在17.12 Edge(mac45)或17.12 Stable(mac46)及更高版本中可用。

  • Docker for Windows上的Kubernetes仅在18.02 Edge(win50)和更高边缘通道中可用。

安装Docker

测试Docker版本号

  • 1.运行docker --version并确保您拥有支持的Docker版本:
1
2
3
docker --version

Docker version 17.12.0-ce, build c97c6d6
  • 2.运行docker info或者(docker version没有--)来查看关于您的Docker安装的更多细节:
1
2
3
4
5
6
7
8
9
10
docker info

Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.12.0-ce
Storage Driver: overlay2
...

为了避免权限错误(以及sudo的使用),将您的用户添加到docker组。更多

测试Docker的安装

  • 1.通过运行简单的Docker镜像hello-world来测试您的安装是否工作正常:
1
2
3
4
5
6
7
8
9
10
11
docker run hello-world

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
ca4f61b1923c: Pull complete
Digest: sha256:ca0eeb6fb05351dfc8759c20733c91def84cb8007aa89a5bf606bc8b315b9fc7
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
...
  • 2.列出下载到你机器上的hello-world镜像:
1
docker image ls
  • 3.列出显示其消息后退出的hello-world容器(由镜像生成)。如果它仍在运行,则不需要--all选项:
1
2
3
4
docker container ls --all

CONTAINER ID IMAGE COMMAND CREATED STATUS
54f4984ed6a8 hello-world "/hello" 20 seconds ago Exited (0) 19 seconds ago

本节回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## List Docker CLI commands
docker
docker container --help

## Display Docker version and info
docker --version
docker version
docker info

## Execute Docker image
docker run hello-world

## List Docker images
docker image ls

## List Docker containers (running, all, all in quiet mode)
docker container ls
docker container ls --all
docker container ls -aq

Part1结论

容器化使CI/CD无缝衔接。例如:

  • 应用程序没有系统依赖关系
  • 更新可以推送到分布式应用程序的任何部分
  • 资源密度可以被优化。

使用Docker,扩展应用程序的过程就是启动新的可执行文件,而不是运行繁重的VM主机。

原文链接

概述

如果你有一些R或者Python的经验,并且掌握一些基本的机器学习知识。对于完成机器学习在线课程的数据科学学生来说,这是一个很适合练手的比赛。

简介

如果让一个想要买房的人来描述他们梦想中的住宅,他们可能不会从地下室天花板的高度或与东西方地铁的距离开始描述。但是这个游乐场比赛的数据集证明,购房者考虑的主要因素中,对价格谈判的影响远远超过卧室或白色栅栏的数量的影响。

有79个解释变量描述(几乎)爱荷华州埃姆斯的住宅房屋的每个方面,这个竞赛的目标是需要你来预测每个房屋的最终价格。

实践技能

  • 创意特征工程
  • 先进的回归算法技术,如随机森林和梯度提升

致谢

Ames Housing数据集由Dean De Cock编制,用于数据科学教育。对于那些寻找比Boston Housing数据集更现代化的扩展版本数据集的数据科学家来说,这的确是一个很赞的选择。

数据

文件描述

  • train.csv - 训练集
  • test.csv - 测试集
  • data_description.txt - 记录了每个特征的完整描述信息,最初由Dean De Cock编写,后来做了略微的改动
  • sample_submission.csv - 来自销售年份和月份的线性回归的基准提交,批量平方英尺和卧室数量(a benchmark submission from a linear regression on year and month of sale, lot square footage, and number of bedrooms)

字段信息

以下是您可以在数据描述文件中找到的简要版本。

字段名 英文解释 中文解释
SalePrice the property’s sale price in dollars. This is the target variable that you’re trying to predict. 房屋的销售价格以美元计价。这是你试图预测的目标变量。
MSSubClass The building class 建筑类
MSZoning The general zoning classification 一般分区分类
LotFrontage Linear feet of street connected to property 连接到财产的街道的线性脚
LotArea Lot size in square feet 地块面积(平方英尺)
Street Type of road access 道路通行类型
Alley Type of alley access 胡同通道的类型
LotShape General shape of property 财产的一般形状
LandContour Flatness of the property 物业的平整度
Utilities Type of utilities available 可用的实用程序类型
LotConfig Lot configuration 批量配置
LandSlope Slope of property 财产的倾斜
Neighborhood Physical locations within Ames city limits Ames城市限制内的物理位置
Condition1 Proximity to main road or railroad 靠近主干道或铁路
Condition2 Proximity to main road or railroad (if a second is present) 靠近主要道路或铁路(如果存在第二个)
BldgType Type of dwelling 住宅类型
HouseStyle Style of dwelling 住宅风格
OverallQual Overall material and finish quality 总体材料和加工质量
OverallCond Overall condition rating 总体状况的评价
YearBuilt Original construction date 原始施工日期
YearRemodAdd Remodel date 重构日期
RoofStyle Type of roof 屋顶类型
RoofMatl Roof material 屋顶材料
Exterior1st Exterior covering on house 房屋外墙
Exterior2nd Exterior covering on house (if more than one material) 房屋外墙(如果多于一种)
MasVnrType Masonry veneer type Masonry贴面类型
MasVnrArea Masonry veneer area in square feet 砖石面积平方英尺
ExterQual Exterior material quality 外部材料质量
ExterCond Present condition of the material on the exterior 外部材料的现状
Foundation Type of foundation 基础类型
BsmtQual Height of the basement 地下室的高度
BsmtCond General condition of the basement 地下室的一般状况
BsmtExposure Walkout or garden level basement walls 罢工或花园级地下室的墙壁
BsmtFinType1 Quality of basement finished area 地下室成品面积质量
BsmtFinSF1 Type 1 finished square feet 1型方形脚
BsmtFinType2 Quality of second finished area (if present) 第二个完成区域的质量(如果存在)
BsmtFinSF2 Type 2 finished square feet 2型完成的平方英尺
BsmtUnfSF Unfinished square feet of basement area 未完成的地下室面积
TotalBsmtSF Total square feet of basement area 地下室面积的平方英尺
Heating Type of heating 加热类型
HeatingQC Heating quality and condition 供热质量和条件
CentralAir Central air conditioning 中央空调
Electrical Electrical system 电气系统
1stFlrSF First Floor square feet 一楼平方英尺
2ndFlrSF Second floor square feet 二楼平方英尺
LowQualFinSF Low quality finished square feet (all floors) 低质量成品平方英尺(所有楼层)
GrLivArea Above grade (ground) living area square feet 以上(地面)生活区平方英尺
BsmtFullBath Basement full bathrooms 地下室完整的浴室
BsmtHalfBath Basement half bathrooms 地下室半浴室
FullBath Full bathrooms above grade 全年以上的浴室
HalfBath Half baths above grade 半浴半高
Bedroom Number of bedrooms above basement level 地下室数量
Kitchen Number of kitchens 厨房数量
KitchenQual Kitchen quality 厨房质量
TotRmsAbvGrd Total rooms above grade (does not include bathrooms) 房间总数(不含浴室)
Functional Home functionality rating 家庭功能评级
Fireplaces Number of fireplaces 壁炉数量
FireplaceQu Fireplace quality 壁炉质量
GarageType Garage location 车库位置
GarageYrBlt Year garage was built 年建车库
GarageFinish Interior finish of the garage 车库内部装修
GarageCars Size of garage in car capacity 车库的车库容量
GarageArea Size of garage in square feet 平方英尺车库大小
GarageQual Garage quality 车库质量
GarageCond Garage condition 车库条件
PavedDrive Paved driveway 铺设的车道
WoodDeckSF Wood deck area in square feet 木甲板面积平方英尺
OpenPorchSF Open porch area in square feet 平方英尺开放门廊
EnclosedPorch Enclosed porch area in square feet 封闭的门廊面积平方英尺
3SsnPorch Three season porch area in square feet 三季门廊面积平方英尺
ScreenPorch Screen porch area in square feet 屏幕门廊面积平方英尺
PoolArea Pool area in square feet 游泳池面积平方英尺
PoolQC Pool quality 游泳池质量
Fence Fence quality 栅栏质量
MiscFeature Miscellaneous feature not covered in other categories 其他类别未涉及的其他功能
MiscVal $Value of miscellaneous feature $杂项功能的值
MoSold Month Sold 月销售
YrSold Year Sold 年销售
SaleType Type of sale 销售类型
SaleCondition Condition of sale 销售条件

用Python进行全面的数据探索

Pedro Marcelino 创建

link

‘The most difficult thing in life is to know yourself’

这句话引用自古希腊米利都的Thales。Thales是希腊/印第安哲学家,数学家和天文学家,被公认为西方文明中第一位享有娱乐和参与科学思想的人(来源:https://en.wikipedia.org/wiki/Thales)。

我不会说了解数据是数据科学中最困难的事情,但这的确是一件非常耗时的事情。很多人可能会忽略这一步骤,就直接下水了。

所以我试着在下水之前先学会游泳。基于Hair等人(2013)整理的’Examining your data’一章中,我尽我所能对数据进行全面而非详尽的分析。我没有在这个内核中上报严谨的研究过程,但我希望它对社区有所帮助,所以我分享了我如何将这些数据分析原理应用于这个问题的思路。

尽管我给这些章写了一些奇怪的名字,但我们在这个内核中所做的是:

  • 1.理解问题:我们将研究每个变量,并对这个问题的意义和重要性进行哲学分析。
  • 2.单变量研究:我们只关注因变量(’SalePrice’)并尝试更多地了解它。
  • 3.多变量研究:我们将尝试了解因变量和自变量之间的关系。
  • 4.基本的清理工作:我们将清理数据集并处理缺失的数据,异常值和分类变量。
  • 5.测试假设:我们将检查我们的数据是否符合大多数多元技术所需的假设。

现在,让我们好好玩吧!

1
2
3
4
5
6
7
8
9
10
11
# 邀请人们来参加Kaggle聚会
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy.stats import norm
from sklearn.preprocessing import StandardScaler
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
1
2
# 导入数据
df_train = pd.read_csv('../input/train.csv')
1
2
# 检查描述信息
df_train.columns

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Index(['Id', 'MSSubClass', 'MSZoning', 'LotFrontage', 'LotArea', 'Street',
'Alley', 'LotShape', 'LandContour', 'Utilities', 'LotConfig',
'LandSlope', 'Neighborhood', 'Condition1', 'Condition2', 'BldgType',
'HouseStyle', 'OverallQual', 'OverallCond', 'YearBuilt', 'YearRemodAdd',
'RoofStyle', 'RoofMatl', 'Exterior1st', 'Exterior2nd', 'MasVnrType',
'MasVnrArea', 'ExterQual', 'ExterCond', 'Foundation', 'BsmtQual',
'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinSF1',
'BsmtFinType2', 'BsmtFinSF2', 'BsmtUnfSF', 'TotalBsmtSF', 'Heating',
'HeatingQC', 'CentralAir', 'Electrical', '1stFlrSF', '2ndFlrSF',
'LowQualFinSF', 'GrLivArea', 'BsmtFullBath', 'BsmtHalfBath', 'FullBath',
'HalfBath', 'BedroomAbvGr', 'KitchenAbvGr', 'KitchenQual',
'TotRmsAbvGrd', 'Functional', 'Fireplaces', 'FireplaceQu', 'GarageType',
'GarageYrBlt', 'GarageFinish', 'GarageCars', 'GarageArea', 'GarageQual',
'GarageCond', 'PavedDrive', 'WoodDeckSF', 'OpenPorchSF',
'EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea', 'PoolQC',
'Fence', 'MiscFeature', 'MiscVal', 'MoSold', 'YrSold', 'SaleType',
'SaleCondition', 'SalePrice'],
dtype='object')

1.那么,我们能得到什么呢?

为了理解我们的数据,我们可以看看每个变量,并试图理解它们的含义以及与这个问题的相关性。我知道这个工作很耗时,但它会给我们数据集增添一些味道。

为了在我们的分析中掌握一些规则,我们可以创建一个Excel电子表格,其中包含以下列:

  • 变量名 - 变量名称。
  • 类型 - 识别变量的类型。该字段有两种可能的值:’数值型’或’类别型’。“数值型”是指值为数字的变量,而“类别型”是指值为类别的变量。
  • 细分 - 识别变量的细分。我们可以定义三个可能的部分:building,space或location。当我们说“building”时,我们是指与建筑物的物理特性相关的变量(例如’OverallQual’<总体材料和加工质量>)。当我们说“space”时,我们是指报告房屋空间属性的变量(例如’TotalBsmtSF’<地下室面积的平方英尺>)。最后,当我们说’location’时,我们说的是指能提供有关房屋所在地的信息(例如’Neighborhood’)的变量。
  • 期望 - 我们对’SalePrice’中的可变影响力的期望。我们可以使用“高”,“中”和“低”作为可能值的分类比例。
  • 结论 - 在我们快速查看数据后,可以得出关于变量重要性的结论。我们可以保持与“期望”相同的分类尺度。
  • 评论 - 我们手动赋予的通用评论信息。

虽然“类型”和“细分”仅供以后使用参考,但“期望”一栏非常重要,因为它有助于我们发展我们的“第六感”。为了填补这个专栏,我们应该阅读所有变量的描述,并逐个问自己:

  • 当我们买房子时,我们是否考虑这个变量?(例如,当我们想到梦想中的房子时,我们是否在意它的’砌体贴面类型’?)
  • 如果是这样,这个变量有多重要?(例如,外部材料这个属性的影响到底是“非常大”还是“非常小”,或者是“一般”呢)?
  • 这些信息是否已在任何其他变量中描述过?(例如,如果’LandContour’<物业的平整度>给出了房产的平坦性,我们是否真的需要知道’LandSlope’<物业的倾斜度>?)。

经过这个艰巨的练习之后,我们可以过滤电子表格并仔细查看具有“高”’期望’的变量。然后,我们可以绘制出这些变量和’SalePrice’之间的一些散点图,填入’结论’栏,这只是我们预期的修正。

我经历了这个过程并得出结论,下面的变量可以在这个问题中发挥重要作用:

  • OverallQual<总体材料和加工质量>(这是一个我不喜欢的变量,因为我不知道它是如何计算的;你可以把使用所有其他可用变量来预测’OverallQual’来作为一个有趣的练习)。
  • YearBuilt<原始施工日期>
  • TotalBsmtSF<地下室面积的平方英尺>
  • GrLivArea<以上(地面)生活区平方英尺>

我选择了两个’building’变量(’OverallQual’和’YearBuilt’)和两个’space’变量(’TotalBsmtSF’和’GrLivArea’)。这可能有点意外,因为它违背了房地产的核心,即在房地产中重要的影响因素是“位置”、“位置”和“位置”。对于类别变量,这种快速数据检查过程可能有点苛刻。例如,我预计’Neigborhood’变量更具相关性,但在数据检查之后,我最终排除了它。也许这与使用散点图而不是箱图有关,它更适合分类变量可视化。我们对数据进行可视化的方式通常会影响到我们的结论。

但是,这个练习的要点是想一想我们的数据和期望值,所以我认为我们达到了目标。现在是’少一点谈话,多一点行动’的时候了。让我们开始吧!

2.首先要做的是分析’SalePrice’

‘SalePrice’是我们所追求的理由。就像我们要参加派对时一样。我们总是有一个理由去那里。比如,和女性交往也许就是我们去参加派对的原因。 (免责声明:根据您的喜好将其适应男性,跳舞或酒精)。

让我们来构建一个使用女性来比喻‘SalePrice’的小故事 — ‘我们如何认识’SalePrice’’的故事。

这一切都始于我们的Kaggle派对。当我们在舞池里寻找一段时间舞伴之后,我们看到一个女孩在酒吧附近使用舞蹈鞋。这说明了她在那里跳舞。我们花了很多时间进行预测建模并参与分析竞赛,因此与女孩谈话并不是我们的主要能力之一。即便如此,我们试了一下:

嗨,我是Kaggly!你呢? ‘SalePrice’?多么美丽的名字!你知道’SalePrice’,你能给我提供一些关于你的数据吗?我刚刚开发了一个模型来计算两个人之间关系成功的可能性。我想在我们身上试一试!’

1
2
# 描述性统计数据汇总
df_train['SalePrice'].describe()
1
2
3
4
5
6
7
8
9
count      1460.000000
mean 180921.195890
std 79442.502883
min 34900.000000
25% 129975.000000
50% 163000.000000
75% 214000.000000
max 755000.000000
Name: SalePrice, dtype: float64

很好……看起来你的最低价格大于零。很棒!你没有那些会毁掉我的模特的个人特质!你可以寄给我一些你的照片吗?……就像你在沙滩上……或者在健身房里自拍的那种一样?“

1
2
# 直方图
sns.distplot(df_train['SalePrice']);

啊!我看到你在出门的时候使用了seaborn化妆……太优雅了!我也看到你:

  • 偏离正态分布。
  • 有明显的正偏态。
  • 显示尖锐度。

这很有趣!’SalePrice’,你能给我你的身体指标吗?’

1
2
3
# 偏度和峰度
print("Skewness: %f" % df_train['SalePrice'].skew())
print("Kurtosis: %f" % df_train['SalePrice'].kurt())
1
2
Skewness: 1.882876
Kurtosis: 6.536282

‘Amazing!如果我的爱情计算器是正确的,我们的成功概率是97.834657%。我想我们应该再见面!如果下星期五有空,请留下我的电话号码并给我打电话。再见!

‘SalePrice’,她的好友和她的兴趣

选择你将要战斗的地形是重要的军事智慧。离开了“SalePrice”,我们就去了她的Facebook。请注意,这不是在跟踪她。我们只是对她做深入的研究。

据她介绍,我们有一些共同的朋友。除了Chuck Norris之外,我们都知道’GrLivArea’和’TotalBsmtSF’。此外,我们也有共同的兴趣,如’OverallQual’和’YearBuilt’。这看起来我们有希望!

为了充分利用我们的研究成果,我们将首先仔细研究我们的共同朋友的概况,然后我们将重点关注我们的共同利益。

与数值变量的关系

1
2
3
4
# 绘制 GrLivArea/SalePrice 的散点图
var = 'GrLivArea'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

嗯……看起来’SalePrice’和’GrLivArea’是真正的老朋友,具有线性关系。

那么’TotalBsmtSF’呢?

1
2
3
4
# 绘制 TotalBsmtSF/SalePrice 的散点图
var = 'TotalBsmtSF'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

‘TotalBsmtSF’也是’SalePrice’的好朋友,但这似乎是一种更加情感化的关系!在一开始的时候,一切似乎都很顺利,突然间,他们的关系呈现出强烈的线性(指数?)反应,一切都在变化。此外,很明显,有时’TotalBsmtSF’本身就会关闭,并且对’SalePrice’给予零分。

与类别特征的关系

1
2
3
4
5
6
# 绘制 OverallQual/SalePrice 的箱图
var = 'OverallQual'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
f, ax = plt.subplots(figsize=(8, 6))
fig = sns.boxplot(x=var, y="SalePrice", data=data)
fig.axis(ymin=0, ymax=800000);

像所有漂亮女孩一样,’SalePrice’很享受’OverallQual’。提醒自己:考虑麦当劳是否适合作为第一次约会的场所。

1
2
3
4
5
6
var = 'YearBuilt'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
f, ax = plt.subplots(figsize=(16, 8))
fig = sns.boxplot(x=var, y="SalePrice", data=data)
fig.axis(ymin=0, ymax=800000);
plt.xticks(rotation=90);

虽然这不是一个强烈的趋势,但我认为相比旧的东西,’SalePrice’更喜欢在新的东西上花钱

注意:我们不知道’SalePrice’是否处于不变价格。不变的价格试图消除通货膨胀的影响。如果’SalePrice’价格不是固定的,那么它应该是这样的,因为多年来价格是可比的。

综上所述,我们可以得出以下结论:

  • ‘GrLivArea’和’TotalBsmtSF’似乎与’SalePrice’线性相关。这两种关系都是积极的,这意味着随着一个变量增加,另一个变量也增加。在’TotalBsmtSF’的情况下,我们可以看到线性关系的斜率特别高。
  • ‘OverallQual’和’YearBuilt’似乎也与’SalePrice’有关。在’OverallQual’的情况下,这种关系似乎更强一些,箱子图显示了销售价格随整体质量的变化情况。

我们只分析了四个变量,但还有很多其他的我们应该分析。这里的诀窍似乎是选择正确的特征(特征选择),而不是它们之间复杂关系的定义(特征工程)。

3.保持客观,理性工作

到现在为止,我们只是遵循我们的直觉,分析了我们认为重要的变量。尽管我们努力为我们的分析提供客观性,但我们必须说,我们的出发点是主观的。

作为一名工程师,我对这种方法感到不舒服。我所有的教育都是为了培养一个训练有素的头脑,能够抵挡主观性的思维。因为如果在结构工程中扮演主观性的角色,你会发现主观的想法是站不住脚的。

所以,让我们克服惯性,做一个更客观的分析。

‘等离子汤’

“一开始除了等离子汤以外没有其他任何东西。在我们研究宇宙学的时候,我们知道这些短暂的时刻,在很大程度上是推测得出的。然而,科学已经根据今天宇宙已知的情况设计了可能发生的事情的一些草图。’(来源:http://umich.edu/~gs265/bigbang.htm

为了探索宇宙,我们将从一些实用的食谱开始,以理解我们的“等离子汤”:

  • 相关矩阵(热图样式)。
  • ‘SalePrice’相关矩阵(放大热图样式)。
  • 最相关的变量之间的散点图(move like Jagger样式)

相关矩阵(热图样式)

1
2
3
4
#相关矩阵
corrmat = df_train.corr()
f, ax = plt.subplots(figsize=(12, 9))
sns.heatmap(corrmat, vmax=.8, square=True);

在我看来,这张热图是快速了解我们的“等离子汤”及其关系的最佳方式。 (谢谢你@seaborn!)

乍一看,有两个红色的正方形引起了我的注意。第一个引用’TotalBsmtSF’和’1stFlrSF’变量,第二个引用’GarageX’变量。两种情况都表明这些变量之间的相关性有多大。实际上,这种相关性非常强,可以表明多重共线性的情况。如果我们考察了这些变量,我们可以得出相同的结论。热图非常适合检测这种情况,并且在像我们这样的特征选择占主导地位的问题中,它们是必不可少的工具。

另一件引起我注意的事情是’SalePrice’相关性。我们可以看到我们众所周知的’GrLivArea’,’TotalBsmtSF’和’OverallQual’这样明显的变量在向我们说“Hi!”,但我们也可以看到许多其他应该考虑的变量。这就是我们接下来要做的。

‘SalePrice’相关矩阵(放大热图样式)

1
2
3
4
5
6
7
# SalePrice相关矩阵
k = 10 #热图的变量数
cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index
cm = np.corrcoef(df_train[cols].values.T)
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10}, yticklabels=cols.values, xticklabels=cols.values)
plt.show()

根据我们的水晶球显示,这些是与“SalePrice”最相关的变量。因此我得出以下结论:

  • OverallQual’,’GrLivArea’和’TotalBsmtSF’与’SalePrice’密切相关。需要检查!
  • ‘GarageCars’和’GarageArea’也是一些与强度相关的变量。但是,正如我们在最后一点所讨论的那样,车库所能容纳的车辆数量是车库面积的结果。’GarageCars’和’GarageArea’就像孪生兄弟。你永远无法区分它们。因此,我们只需要分析其中的一个变量(我们可以保留’GarageCars’,因为它与’SalePrice’的关联性更高)。
  • ‘TotalBsmtSF’和’1stFloor’也似乎是双胞胎兄弟。我们可以只保留’TotalBsmtSF’(重新阅读’那么,我们能得到什么呢?’部分)。
  • ‘FullBath’?? 真的需要吗?
  • ‘TotRmsAbvGrd’和’GrLivArea’,再次是双胞胎兄弟。这是来自切尔诺贝利的数据集吗?
  • 啊……’YearBuilt’……看起来’YearBuilt’与’SalePrice’略有关联。老实说,对于’YearBuilt’变量来说,我是有一些额外的顾虑的,因为这让我觉得我们应该做一些时间序列分析来分析这一变量。我会把这个问题作为你的homework。

我们继续看散点图。

‘SalePrice’和相关变量之间的散点图(move like Jagger style)

前方高能!我第一次看到这些散点图的时候,我完全震惊了。

在如此短的空间里有如此多的信息.​​…..这真是太神奇了。再一次谢谢@seaborn!你让我’move like Jagger’!

1
2
3
4
5
# 绘制散点图
sns.set()
cols = ['SalePrice', 'OverallQual', 'GrLivArea', 'GarageCars', 'TotalBsmtSF', 'FullBath', 'YearBuilt']
sns.pairplot(df_train[cols], size = 2.5)
plt.show();

虽然我们已经知道一些主要的变量,但这个巨大的散点图给了我们关于变量关系的一个合理的解释。

我们可能会对’TotalBsmtSF’和’GrLiveArea’组成的散点图感兴趣。在这个图中,我们可以看到许多点画出了一条线,几乎就像一个边界。这种结果是完全有道理的,并且大多数的点保持在该线以下。地下室的面积可以等于地面上的居住面积,但预计地下室面积不会超过地上居住面积(除非你想购买的是地堡)。

关于’SalePrice’和’YearBuilt’的情况也可以让我们进一步思考。在“点云”的底部,我们看到一个看起来几乎是一个指数函数的曲线。我们也可以在’点云’的上限中看到同样的趋势。另外,请注意过去几年中的一系列点数是如何保持在这个极限之上的(我只是想说价格增速正在变快)。

好吧,现在我们已经完成了足够多的罗夏测试。让我们来探讨下一步的内容:缺失数据!

4.缺失的数据

在考虑缺失数据时的几个重要问题:

  • 缺失的数据有多普遍?
  • 丢失数据是随机现象的还是有一定的规律?

这些问题的答案很重要,因为缺少数据可能意味着样本量减少。这可能会使我们的分析工作无法进行。此外,从实质的角度来看,我们需要确保缺失的数据流程没有偏见,并确保没有将一些不易洞察的事实所隐藏。

1
2
3
4
5
# 缺失数据
total = df_train.isnull().sum().sort_values(ascending=False)
percent = (df_train.isnull().sum()/df_train.isnull().count()).sort_values(ascending=False)
missing_data = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
missing_data.head(20)
Total Percent
PoolQC 1453 0.995205
MiscFeature 1406 0.963014
Alley 1369 0.937671
Fence 1179 0.807534
FireplaceQu 690 0.472603
LotFrontage 259 0.177397
GarageCond 81 0.055479
GarageType 81 0.055479
GarageYrBlt 81 0.055479
GarageFinish 81 0.055479
GarageQual 81 0.055479
BsmtExposure 38 0.026027
BsmtFinType2 38 0.026027
BsmtFinType1 37 0.025342
BsmtCond 37 0.025342
BsmtQual 37 0.025342
MasVnrArea 8 0.005479
MasVnrType 8 0.005479
Electrical 1 0.000685
Utilities 0 0.000000

让我们分析一下这个表里的信息,来理解如何处理丢失的数据。

我们会考虑当超过15%的数据丢失时,删除相应的变量并假设它从来都不存在。这意味着在这些情况下,我们不会尝试填补缺失数据。按照这个逻辑,我们应该删除一组变量(例如’PoolQC’,’MiscFeature’,’Alley’等)。这么做的重点是:我们会错过这些数据吗?我不这么认为。这些变量中没有一个看起来很重要,因为其中大多数不是我们在购买房屋时考虑的方面(或许这就是这些数据会缺失的原因?)。此外,仔细观察变量,像’PoolQC’,’MiscFeature’和’FireplaceQu’这样的变量是异常值的强有力候选者,所以我们很乐意删除它们。

在其余的案例中,我们可以看到“GarageX”系列的变量具有相同数量的缺失数据。我敢打赌,缺少的数据来自同一组观察结果(我不会检查它,缺失值只有5%的占比)。由于表示关于车库信息的最重要信息的字段是“GarageCars”,并且考虑到我们只是谈论了5%的缺失数据,所以我将删除提及的“GarageX”变量。同样的逻辑适用于’BsmtX’变量。

关于’MasVnrArea’和’MasVnrType’,我们可以认为这些变量不是必需的。此外,它们与已经考虑过的“YearBuilt”和“OverallQual”有很强的相关性。因此,如果我们删除’MasVnrArea’和’MasVnrType’这两个变量,我们不会丢失信息。

最后,我们在’Electrical’中有一个缺失的观察。由于这只是一个观察,我们将删除此记录并保留该变量。

总之,为了处理缺失的数据,我们将删除所有缺少数据的变量,但变量’Electrical’除外。在’Electrical’中,我们只需删除缺少数据的观察结果。

1
2
3
4
# 缺失数据处理
df_train = df_train.drop((missing_data[missing_data['Total'] > 1]).index,1)
df_train = df_train.drop(df_train.loc[df_train['Electrical'].isnull()].index)
df_train.isnull().sum().max() #just checking that there's no missing data missing...
1
0

离群值!

离群值也是我们应该意识到的。为什么?因为异常值可以显着影响我们的模型,并且可以成为宝贵的信息来源,为某些特定行为提供解释。

离群值是一个复杂的问题,值得给与更多关注。在这里,我们将通过“SalePrice”的标准偏差和一组散点图进行快速分析。

单变量分析

这里主要关心的是建立一个阈值,将观测定义为异常值。为此,我们将标准化数据。在此情况下,数据标准化意味着将数据值转换为平均值为0,标准偏差为1。

1
2
3
4
5
6
7
8
# 标准化数据
saleprice_scaled = StandardScaler().fit_transform(df_train['SalePrice'][:,np.newaxis]);
low_range = saleprice_scaled[saleprice_scaled[:,0].argsort()][:10]
high_range= saleprice_scaled[saleprice_scaled[:,0].argsort()][-10:]
print('outer range (low) of the distribution:')
print(low_range)
print('\nouter range (high) of the distribution:')
print(high_range)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
outer range (low) of the distribution:
[[-1.83820775]
[-1.83303414]
[-1.80044422]
[-1.78282123]
[-1.77400974]
[-1.62295562]
[-1.6166617 ]
[-1.58519209]
[-1.58519209]
[-1.57269236]]

outer range (high) of the distribution:
[[3.82758058]
[4.0395221 ]
[4.49473628]
[4.70872962]
[4.728631 ]
[5.06034585]
[5.42191907]
[5.58987866]
[7.10041987]
[7.22629831]]

对’SalePrice’的新衣服,感觉如何?:

  • 低范围值与0相似且不太远。
  • 高范围值远离0,大概是7.多的值。

目前,我们不会将这些值视为异常值,但我们应该小心这两个值。

双变量分析

我们已经了解了以下散点图。但是,当我们从新的角度来看事物时,总会有一些东西需要发现。正如Alan Kay所说,“视角的改变相当于提高80点智商”。

1
2
3
4
# 双变量分析SalePrice/GrLivArea
var = 'GrLivArea'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

它所揭示的是:

  • 这两个变量中,更大的’GrLivArea’的数据看起来很奇怪,这些较大的’GrLivArea’离群了。我们可以推测为什么会发生这种情况。也许这些样本是农业领域的,这样想可以解释低价的原因。我不确定这一点,但我确信这两点并不代表典型案例。因此,我们将它们定义为异常值并删除它们。
  • 顶部的两个观察结果是那些7.多的那两个,我们应该小心处理这些观察结果。他们看起来像两个特殊情况,但他们似乎正在追随上涨的趋势。出于这个原因,我们会保留它们。
1
2
3
4
# 删除点
df_train.sort_values(by = 'GrLivArea', ascending = False)[:2]
df_train = df_train.drop(df_train[df_train['Id'] == 1299].index)
df_train = df_train.drop(df_train[df_train['Id'] == 524].index)
1
2
3
4
# 双变量分析SalePrice/GrLivArea
var = 'TotalBsmtSF'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

我们可以想象消除一些观察结果(例如TotalBsmtSF> 3000的结果),但我认为这么做不值得。我们可以接受这些值,所以我们不会做任何事情。

5.硬核部分

在Ayn Rand的小说“阿特拉斯耸耸肩”中,有一个经常重复的问题:John Galt是谁?本书很大一部分是关于寻求发现这个问题的答案。

我现在体会到了Rand的感受,’SalePrice’是谁呢?

这个问题的答案在于测试多变量分析统计基础的假设。我们已经做了一些数据清理,并发现了很多关于’SalePrice’的信息。现在是深入了解’SalePrice’如何符合统计假设的时候了,这些假设使我们能够应用多元技术。

根据Hair et al. (2013),我们应该测试四个假设:

  • 正态性 - 当我们谈论正态性时,我们的意思是数据应该看起来像正态分布。这很重要,因为几个统计测试依赖于此(例如t-statistics)。在本练习中,我们将检查’SalePrice’的单变量正态性(这是一种有限的方法)。请记住,单变量正态性并不能确保多元正态性(这是我们想要的),但这么做是有帮助的。需要考虑的另一个细节是,在大样本(> 200个观测值)的情况下,正态性不是一个重要的问题。但是,如果我们解决正态性问题,就可以避免很多其他问题(例如异质性),这就是我们进行这种分析的主要原因。
  • 方差齐性 - 希望我写的是对的。 方差齐性指的是“假设变量(一个或多个)在预测​​变量范围内表现出相同的方差水平”(Hair et al。,2013)。考虑方差齐性是合理的,因为我们希望误差项在自变量的所有值中都是相同的。
  • 线性 - 评估线性的最常见方法是检查散点图并搜索线性模式。如果模式不是线性的,那么可以尝试探索数据转换。但是,在这里我们去转换数据,因为我们看到的大多数散点图似乎都具有线性关系。
  • 缺少相关错误 - 如定义所示,相关的错误发生在一个错误与另一个错误相关时。例如,如果一个正误差系统地产生负误差,则意味着这些变量之间存在关系。这通常以时间序列发生,其中一些模式与时间相关。我们在这里不会涉及这一点。但是,如果您检测到某些内容,请尝试添加一个可以解释您获得的效果的变量。这是相关错误的最常见解决方案。

你认为猫王会对这个漫长的解释说些什么? ‘请少一点谈话,多一点行动’?可能……顺便说一下,你知道Elvis最后一次承受的重大的打击是什么吗?

(…)

是浴室的地板。

寻找正态性

这里的要点是以非常精益的方式测试’SalePrice’。我们将这样做:

  • 直方图 - 峰度和偏度。
  • 正态概率图 - 数据分布应该紧跟代表正态分布的对角线。
1
2
3
4
# 直方图和正态概率图
sns.distplot(df_train['SalePrice'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['SalePrice'], plot=plt)

好吧,’SalePrice’不服从正态分布。它显示’顶峰’呈现出正偏斜状态,并且不遵循对角线。

但一切都没有丢失。简单的数据转换可以解决问题。这是您可以在统计书籍中学到的很棒的东西之一:如果是正偏态,使用log函数转换通常效果不错。当我发现这一点时,我感觉自己就像一个霍格沃茨的学生发现了一个新的咒语一样。

1
2
# 应用log函数转换
df_train['SalePrice'] = np.log(df_train['SalePrice'])
1
2
3
4
# 转换的直方图和正态概率分布图
sns.distplot(df_train['SalePrice'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['SalePrice'], plot=plt)

完成!让我们来看看’GrLivArea’的情况。

1
2
3
4
# 直方图和正态概率分布图
sns.distplot(df_train['GrLivArea'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['GrLivArea'], plot=plt)

看起来有点歪…

1
2
# 数据转换
df_train['GrLivArea'] = np.log(df_train['GrLivArea'])
1
2
3
4
# 转换的直方图和正态概率分布图
sns.distplot(df_train['GrLivArea'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['GrLivArea'], plot=plt)

下一个…

1
2
3
4
# 直方图和正态概率分布图
sns.distplot(df_train['TotalBsmtSF'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['TotalBsmtSF'], plot=plt)

好的,现在我们要处理几个大麻烦了:

  • 总体来说,数据分布呈现偏斜状态。
  • 有大量的零值观察(比如没有地下室的房屋)。
  • 0是不能求log的,这是一个很大的问题。

为了在这里应用log转换,我们将创建一个变量来获得有或没有地下室(二进制变量)的效果。然后,我们将对所有非零观测值进行对数转换,忽略零值。这样我们就可以转换数据,而不会失去有或没有地下室的影响。

我不确定这种方法是否正确。但这对我来说似乎是正确的。这就是我所说的“高风险工程”。

1
2
3
4
5
# 为新变量创建列(一个足够了,因为它是一个二进制分类特征)
# 如果 area>0 值为1,否则 值为0
df_train['HasBsmt'] = pd.Series(len(df_train['TotalBsmtSF']), index=df_train.index)
df_train['HasBsmt'] = 0
df_train.loc[df_train['TotalBsmtSF']>0,'HasBsmt'] = 1
1
2
# 数据转换
df_train.loc[df_train['HasBsmt']==1,'TotalBsmtSF'] = np.log(df_train['TotalBsmtSF'])
1
2
3
4
# 直方图和正态概率分布图
sns.distplot(df_train[df_train['TotalBsmtSF']>0]['TotalBsmtSF'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train[df_train['TotalBsmtSF']>0]['TotalBsmtSF'], plot=plt)

在第一次尝试中寻找“方差齐性”

测试两个度量变量的同方差性的最佳方法是图形化。对不同特征使用相同的分布方式,生成锥形(样本在一侧分布的少,另一侧分布的多)或钻石(大量点分布在中心区域)等形状的分布情况。

绘制SalePrice’和’GrLivArea’的分布:

1
2
#散点图
plt.scatter(df_train['GrLivArea'], df_train['SalePrice']);

此散点图的旧版本(在对数转换之前)具有圆锥形状(返回并检查’SalePrice’和相关变量之间的散点图(move like Jagger style))。如您所见,当前的散点图不再具有圆锥形状。这是正态化的力量!只要确保一些变量的正态性,我们解决了同方差问题。

我们继续检查’SalePrice’和’TotalBsmtSF’。

1
2
#散点图
plt.scatter(df_train[df_train['TotalBsmtSF']>0]['TotalBsmtSF'], df_train[df_train['TotalBsmtSF']>0]['SalePrice']);

总的来说,’SalePrice’在’TotalBsmtSF’范围内表现出相等的变化水平。Cool!

Last but not the least, 虚拟变量

简单模式。

1
2
# 虚拟变量转换
df_train = pd.get_dummies(df_train)

结论

到了练习的最后部分。

在整个这个kernel中,我们实践了许多由Hair等人提出的策略。 (2013年)。我们对变量进行了哲学研究,我们单独分析了“SalePrice”,并且与最相关的变量进行了分析,我们处理了缺失的数据和异常值,我们测试了一些基本的统计假设,甚至将分类变量转换为虚拟变量。 Python帮助我们轻松完成了很多工作。

但是我们的任务还没有结束。别忘了,我们的故事还停留在对’SalePrice’的Facebook的研究那一步呢。现在是时候打电话给’SalePrice’并邀请她共进晚餐。试着预测她的行为。你认为她是一个喜欢正规化线性回归方法的女孩吗?或者你认为她喜欢合奏方法?或者也许别的东西?

答案由你找出。

参考

作者blog

Hair等人,2013,多变量数据分析,第7版

致谢

感谢JoãoRico的审阅。

原创文章,转载请注明出处

首先安利一波广告:

最近利用业余时间搭建了一个智能量化投资平台,代码已经全部开源:https://github.com/DannyLee1991/ai_qi

目前已经实现的功能有:

  • 数据的抓取(数据来源tushare})

  • 数据入库,并在界面上可以执行sql操作

  • 数据可视化


  • 数据集创建

数据集管理

数据集创建

数据集查看

  • 数据预处理

// 暂无界面

正在开发中的功能:

  • 数据建模
  • 接入交易接口

各个已有的功能目前也只是做了部分实现,先把各个环节打通,然后在慢慢填充。最终的目标是利用机器学习算法来预测分析各种投资数据。

欢迎各位大牛拍砖指导~


好,以上不是本文的重点,本文重点是这两天在建立第一个模型过程中遇到的一个坑。


数据集介绍

第一个模型,我准备使用日交易数据来预测次日的涨跌幅度,使用的数据来自日交易数据表:

这是对应的原始数据集详情信息:

其中 除了X的‘date’(时间)特征之外,其他的特征都是float类型的数据。暂时先剔除这一维度的数据,所以最后用来训练的X的shape是(292583, 15)

Y的shape不变,是(292583, 1)

Y的数据类型也是float。

这里先解释一下Y的含义:Y对应的数据是涨跌幅,但和与之对应的X的数据并不是同一天的,这里日期间隔1的含义是Y取的是相对于X的数据的时间的下一日的数据,因为我们要预测的是次日涨跌幅。

对于分类模型,我们的Y应该是类别标签,而不能是连续型数值,所以我们应该把当前的float类型的Y,转换成某种类别标签来表示。

一种很容易理解的方式,就是float转int,简单粗暴:

eg:

1
Y = [1.1, 2.6, 3.5, -1.8, ...]

转换为int之后

1
Y = [1, 2, 3, -1, ...]

但这里有一个小坑,负数的标签在带入到tensorflow中训练是会报错的,所以把标签数据+10,全部转为大于等于0的数据,处理之后的效果为:

1
Y = [11, 12, 13, 9, ...]

在经过上述的预处理之后,Y被处理成了21个类别(-10到10之间一共21个int值)的数据集。

然后就是将数据打乱,按照6:2:2的比例将数据分割为训练集验证集测试集


带入训练

仿照tensorflow的demo,将数据带入一个DNN模型,进行训练。

没有进行什么特殊的调参,最终准确率竟然达到了接近50%!

要知道这是21个类别的分类问题,50%的准确率已经远远高于均匀分布情况下随机选择的准确率(大约是4.8%)了。

于是我觉得21类数据,准确率就能达到这么高,那如果我将类别改为2类,准确率应该会大幅提升的。

于是我重新将Y的数据改为了0和1两种类别,0代表跌1代表涨。

但实际情况,并没有好很多,准确率大概达到52%左右。

看到这种结果,第一时间,我是怀疑自己代码有没有哪里写错,但经过排查,并没有发现有什么异常。

后来,又尝试将数据分为4类,8类来训练,得到的结果依然是50%左右。

WTF?

原来是偏斜类在搞鬼

这里有篇关于偏斜类的文章,值得一看。

我将原始数据绘制成柱状图之后,发现了问题所在:

数据在各个类别上并不是均匀分布的,大部分都聚集在了10(对应涨跌幅为0%)的位置上。而我训练出来的模型,带入一批测试数据后,预测结果也都是10。

也就是说,我们的分类器,就算完全没有识别能力,输入任何值,输出的结果都是10这种类别,那么这个分类器就有50%的准确率!!!

这是一个很傻的结果,就好比一个完全不懂股票的人,你问他某只股票明天会不会涨,他只要回答:“明天不涨不跌”,那么他就有50%的概率猜对了。

好吧,这并不是我们想要的效果,那么我们如何避免这种情况呢?

想办法让各个类别的数据呈现均匀分布

我想到的办法是在标签数据生成的过程中做些手脚。使得各个类别的数据呈现出均匀分布的情况。

所以我写了一个函数,用来将数据处理成均匀分布的标签:

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import numpy as np
import math


def uniform_distribution(Y, n, l_type='b'):
'''
平均分布

将Y按照值的大小进行均匀分布的方式分割成n等分
并生成对应的类别标签

:param Y: 数据源 形状为(num,)
:param n: 将数据分割为n等分
:param l_type: 分割数据之后 标签的取值类别{'b','s','n'}
'b' : 类别标签为区间范围内的最大值
's' : 类别标签为区间范围内的最小值
'n' : 类别标签为区间范围内的平均值

:return:

eg:
Y = [1,1,2,3,4,4,5,6,7]
n = 5
l_type = 'b'
result = [1 1 3 3 4 4 6 6 7]

n = 3
l_type = 's'
result = [1 1 1 2 2 2 4 4 4]

n = 5
l_type = 'n'
result = [ 1. 1. 2. 2. 3.5 3.5 5. 5. 6.5]

'''

node_list = gen_nodelist(Y, n)
toY = []
for num in Y:
# 这里虽然得到的是float类型的值,但tensorflow的类别标签 只能是int类型,所以将对应的索引作为标签值来使用
index, val = get_label(num, node_list, l_type)
toY.append(index)

print("node list >>")
print(node_list)
return np.array(toY)


def get_label(num, node_list, l_type):
# 下限
lm = 0
# 上限
um = 0
target = 0

# 节点区间是(lm,um]
for index, node in enumerate(node_list):
if node >= num:
if index > 0:
um = node
lm = node_list[index - 1]
target = index
break

label = ''
if l_type == 's':
label = lm
elif l_type == 'b':
label = um
elif l_type == 'n':
label = (lm + um) / 2

return target, label


def gen_nodelist(data, n):
'''
生成节点列表 节点是指示数据的分割点
:param data: 原始数据 需要被分割的数据
:param n: 分割的份数
:return:
'''
sorted_list = sorted(data)
size = len(sorted_list)
node_list = []
block_size = math.ceil(size / n)
for index, item in enumerate(sorted_list):
if (index + 1) % block_size == 0:
node_list.append(item)
elif index == 0:
node_list.append(item)
elif index == size - 1:
node_list.append(item)

return node_list

用这个函数重新预处理我们的Y,这里我们将Y分为10种类别,Y按照以下的标签值,重新赋值:

1
[-10.08, -2.3900000000000001, -1.4199999999999999, -0.84999999999999998, -0.40999999999999998, 0.0, 0.28999999999999998, 0.68999999999999995, 1.24, 2.25, 10.16]

可以看到,这些标签值并不是线性的,这是因为我们的数据不是均匀分布的。

按照这种标签处理之后,将数据绘制成柱状图后,如下:

接下来带入训练就比较正常了。

原文地址

这是关于pandas的一个简短的介绍,主要面向的是新手用户。你可以在Cookbook查看更多复杂的使用方式。

通常情况下,我们按照下面这种方式引入:

1
2
3
4
5
In [1]: import pandas as pd

In [2]: import numpy as np

In [3]: import matplotlib.pyplot as plt

对象创建

数据结构介绍部分

通过传入一个list的数值来创建一个Series,pandas会创建一个默认的整数索引:

1
2
3
4
5
6
7
8
9
10
11
In [4]: s = pd.Series([1,3,5,np.nan,6,8])

In [5]: s
Out[5]:
0 1.0
1 3.0
2 5.0
3 NaN
4 6.0
5 8.0
dtype: float64

通过传入一个numpy数组来创建一个’DataFrame’,带有一个datetime的索引以及标签列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [6]: dates = pd.date_range('20130101', periods=6)

In [7]: dates
Out[7]:
DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',
'2013-01-05', '2013-01-06'],
dtype='datetime64[ns]', freq='D')

In [8]: df = pd.DataFrame(np.random.randn(6,4), index=dates, columns=list('ABCD'))

In [9]: df
Out[9]:
A B C D
2013-01-01 0.469112 -0.282863 -1.509059 -1.135632
2013-01-02 1.212112 -0.173215 0.119209 -1.044236
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804
2013-01-04 0.721555 -0.706771 -1.039575 0.271860
2013-01-05 -0.424972 0.567020 0.276232 -1.087401
2013-01-06 -0.673690 0.113648 -1.478427 0.524988

通过传入一个可以转换为类series(series-like)的字典对象来创建一个DataFrame:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [10]: df2 = pd.DataFrame({ 'A' : 1.,
....: 'B' : pd.Timestamp('20130102'),
....: 'C' : pd.Series(1,index=list(range(4)),dtype='float32'),
....: 'D' : np.array([3] * 4,dtype='int32'),
....: 'E' : pd.Categorical(["test","train","test","train"]),
....: 'F' : 'foo' })
....:

In [11]: df2
Out[11]:
A B C D E F
0 1.0 2013-01-02 1.0 3 test foo
1 1.0 2013-01-02 1.0 3 train foo
2 1.0 2013-01-02 1.0 3 test foo
3 1.0 2013-01-02 1.0 3 train foo

查看不同列的数据类型:

1
2
3
4
5
6
7
8
9
In [12]: df2.dtypes
Out[12]:
A float64
B datetime64[ns]
C float32
D int32
E category
F object
dtype: object

如果你正在使用IPython,使用Tab自动补全功能会自动识别所有的属性以及自定义的列,下图中是所有能够被自动识别的属性的一个子集:

1
2
3
4
5
6
7
8
9
10
11
12
13
In [13]: df2.<TAB>
df2.A df2.bool
df2.abs df2.boxplot
df2.add df2.C
df2.add_prefix df2.clip
df2.add_suffix df2.clip_lower
df2.align df2.clip_upper
df2.all df2.columns
df2.any df2.combine
df2.append df2.combine_first
df2.apply df2.compound
df2.applymap df2.consolidate
df2.D

正如你所看的,这里的列A,B,CD是自动补全的,为了简洁,其余的属性被截断。

查看数据

详情请参阅:Basics section

查看frame中头部和尾部的行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [14]: df.head()
Out[14]:
A B C D
2013-01-01 0.469112 -0.282863 -1.509059 -1.135632
2013-01-02 1.212112 -0.173215 0.119209 -1.044236
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804
2013-01-04 0.721555 -0.706771 -1.039575 0.271860
2013-01-05 -0.424972 0.567020 0.276232 -1.087401

In [15]: df.tail(3)
Out[15]:
A B C D
2013-01-04 0.721555 -0.706771 -1.039575 0.271860
2013-01-05 -0.424972 0.567020 0.276232 -1.087401
2013-01-06 -0.673690 0.113648 -1.478427 0.524988

显示索引,列和底层numpy数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [16]: df.index
Out[16]:
DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',
'2013-01-05', '2013-01-06'],
dtype='datetime64[ns]', freq='D')

In [17]: df.columns
Out[17]: Index(['A', 'B', 'C', 'D'], dtype='object')

In [18]: df.values
Out[18]:
array([[ 0.4691, -0.2829, -1.5091, -1.1356],
[ 1.2121, -0.1732, 0.1192, -1.0442],
[-0.8618, -2.1046, -0.4949, 1.0718],
[ 0.7216, -0.7068, -1.0396, 0.2719],
[-0.425 , 0.567 , 0.2762, -1.0874],
[-0.6737, 0.1136, -1.4784, 0.525 ]])

describe()函数对于数据的快速统计汇总:

1
2
3
4
5
6
7
8
9
10
11
In [19]: df.describe()
Out[19]:
A B C D
count 6.000000 6.000000 6.000000 6.000000
mean 0.073711 -0.431125 -0.687758 -0.233103
std 0.843157 0.922818 0.779887 0.973118
min -0.861849 -2.104569 -1.509059 -1.135632
25% -0.611510 -0.600794 -1.368714 -1.076610
50% 0.022070 -0.228039 -0.767252 -0.386188
75% 0.658444 0.041933 -0.034326 0.461706
max 1.212112 0.567020 0.276232 1.071804

对数据的转置:

1
2
3
4
5
6
7
8
In [20]: df.T
Out[20]:
2013-01-01 2013-01-02 2013-01-03 2013-01-04 2013-01-05 2013-01-06
A 0.469112 1.212112 -0.861849 0.721555 -0.424972 -0.673690
B -0.282863 -0.173215 -2.104569 -0.706771 0.567020 0.113648
C -1.509059 0.119209 -0.494929 -1.039575 0.276232 -1.478427
D -1.135632 -1.044236 1.071804 0.271860 -1.087401 0.524988

按轴进行排序

1
2
3
4
5
6
7
8
9
In [21]: df.sort_index(axis=1, ascending=False)
Out[21]:
D C B A
2013-01-01 -1.135632 -1.509059 -0.282863 0.469112
2013-01-02 -1.044236 0.119209 -0.173215 1.212112
2013-01-03 1.071804 -0.494929 -2.104569 -0.861849
2013-01-04 0.271860 -1.039575 -0.706771 0.721555
2013-01-05 -1.087401 0.276232 0.567020 -0.424972
2013-01-06 0.524988 -1.478427 0.113648 -0.673690

按值进行排序

1
2
3
4
5
6
7
8
9
In [22]: df.sort_values(by='B')
Out[22]:
A B C D
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804
2013-01-04 0.721555 -0.706771 -1.039575 0.271860
2013-01-01 0.469112 -0.282863 -1.509059 -1.135632
2013-01-02 1.212112 -0.173215 0.119209 -1.044236
2013-01-06 -0.673690 0.113648 -1.478427 0.524988
2013-01-05 -0.424972 0.567020 0.276232 -1.087401

选择

注意:虽然用于选择和设置的标准的Python/Numpy表达式非常直观,可用于交互式工作,但对于生产代码,我们推荐优化的pandas数据访问方法.at.iat.loc.iloc.ix

详情请参阅Indexing and Selecing DataMultiIndex / Advanced Indexing

获取

选择一个单独的列,这将会返回一个Series,等同于df.A

1
2
3
4
5
6
7
8
9
In [23]: df['A']
Out[23]:
2013-01-01 0.469112
2013-01-02 1.212112
2013-01-03 -0.861849
2013-01-04 0.721555
2013-01-05 -0.424972
2013-01-06 -0.673690
Freq: D, Name: A, dtype: float64

通过[]进行选择,这将会对行进行切片:

1
2
3
4
5
6
7
8
9
10
11
12
13
In [24]: df[0:3]
Out[24]:
A B C D
2013-01-01 0.469112 -0.282863 -1.509059 -1.135632
2013-01-02 1.212112 -0.173215 0.119209 -1.044236
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804

In [25]: df['20130102':'20130104']
Out[25]:
A B C D
2013-01-02 1.212112 -0.173215 0.119209 -1.044236
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804
2013-01-04 0.721555 -0.706771 -1.039575 0.271860

通过标签选择

详情请参加Selection by Label

使用标签获取横截面

1
2
3
4
5
6
7
In [26]: df.loc[dates[0]]
Out[26]:
A 0.469112
B -0.282863
C -1.509059
D -1.135632
Name: 2013-01-01 00:00:00, dtype: float64

通过标签选择多轴

1
2
3
4
5
6
7
8
9
In [27]: df.loc[:,['A','B']]
Out[27]:
A B
2013-01-01 0.469112 -0.282863
2013-01-02 1.212112 -0.173215
2013-01-03 -0.861849 -2.104569
2013-01-04 0.721555 -0.706771
2013-01-05 -0.424972 0.567020
2013-01-06 -0.673690 0.113648

显示包括两个端点的标签切片:

1
2
3
4
5
6
In [28]: df.loc['20130102':'20130104',['A','B']]
Out[28]:
A B
2013-01-02 1.212112 -0.173215
2013-01-03 -0.861849 -2.104569
2013-01-04 0.721555 -0.706771

减少返回的对象的尺寸:

1
2
3
4
5
In [29]: df.loc['20130102',['A','B']]
Out[29]:
A 1.212112
B -0.173215
Name: 2013-01-02 00:00:00, dtype: float64

获得标量值

1
2
In [30]: df.loc[dates[0],'A']
Out[30]: 0.46911229990718628

快速访问一个标量(与上一个方法等价)

1
2
In [31]: df.at[dates[0],'A']
Out[31]: 0.46911229990718628

按位置选择

详情请参阅Selection by Position

通过传入的整数位置来选择:

1
2
3
4
5
6
7
In [32]: df.iloc[3]
Out[32]:
A 0.721555
B -0.706771
C -1.039575
D 0.271860
Name: 2013-01-04 00:00:00, dtype: float64

可以做类似numpy/python的整数切片操作

1
2
3
4
5
In [33]: df.iloc[3:5,0:2]
Out[33]:
A B
2013-01-04 0.721555 -0.706771
2013-01-05 -0.424972 0.567020

可以做类似于numpy/python风格的列出指定索引位置的行列列表的操作

1
2
3
4
5
6
In [34]: df.iloc[[1,2,4],[0,2]]
Out[34]:
A C
2013-01-02 1.212112 0.119209
2013-01-03 -0.861849 -0.494929
2013-01-05 -0.424972 0.276232

选择具体的行

1
2
3
4
5
In [35]: df.iloc[1:3,:]
Out[35]:
A B C D
2013-01-02 1.212112 -0.173215 0.119209 -1.044236
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804

选择具体的列

1
2
3
4
5
6
7
8
9
In [36]: df.iloc[:,1:3]
Out[36]:
B C
2013-01-01 -0.282863 -1.509059
2013-01-02 -0.173215 0.119209
2013-01-03 -2.104569 -0.494929
2013-01-04 -0.706771 -1.039575
2013-01-05 0.567020 0.276232
2013-01-06 0.113648 -1.478427

明确地获取一个值

1
2
In [37]: df.iloc[1,1]
Out[37]: -0.17321464905330858

快速访问一个标量(等同于上面的方法)

1
2
In [38]: df.iat[1,1]
Out[38]: -0.17321464905330858

布尔索引操作

使用单一列的值来选取数据

1
2
3
4
5
6
In [39]: df[df.A > 0]
Out[39]:
A B C D
2013-01-01 0.469112 -0.282863 -1.509059 -1.135632
2013-01-02 1.212112 -0.173215 0.119209 -1.044236
2013-01-04 0.721555 -0.706771 -1.039575 0.271860

从DataFrame选取符合布尔判断条件的数据

1
2
3
4
5
6
7
8
9
In [40]: df[df > 0]
Out[40]:
A B C D
2013-01-01 0.469112 NaN NaN NaN
2013-01-02 1.212112 NaN 0.119209 NaN
2013-01-03 NaN NaN NaN 1.071804
2013-01-04 0.721555 NaN NaN 0.271860
2013-01-05 NaN 0.567020 0.276232 NaN
2013-01-06 NaN 0.113648 NaN 0.524988

使用isin()方法来过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [41]: df2 = df.copy()

In [42]: df2['E'] = ['one', 'one','two','three','four','three']

In [43]: df2
Out[43]:
A B C D E
2013-01-01 0.469112 -0.282863 -1.509059 -1.135632 one
2013-01-02 1.212112 -0.173215 0.119209 -1.044236 one
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804 two
2013-01-04 0.721555 -0.706771 -1.039575 0.271860 three
2013-01-05 -0.424972 0.567020 0.276232 -1.087401 four
2013-01-06 -0.673690 0.113648 -1.478427 0.524988 three

In [44]: df2[df2['E'].isin(['two','four'])]
Out[44]:
A B C D E
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804 two
2013-01-05 -0.424972 0.567020 0.276232 -1.087401 four

设置数据

设置新列,自动按索引排列数据

1
2
3
4
5
6
7
8
9
10
11
12
13
In [45]: s1 = pd.Series([1,2,3,4,5,6], index=pd.date_range('20130102', periods=6))

In [46]: s1
Out[46]:
2013-01-02 1
2013-01-03 2
2013-01-04 3
2013-01-05 4
2013-01-06 5
2013-01-07 6
Freq: D, dtype: int64

In [47]: df['F'] = s1

通过标签来设定数据

1
In [48]: df.at[dates[0],'A'] = 0

通过位置索引来设定数据

1
In [49]: df.iat[0,1] = 0

通过分配一个numpy数组来设定数据

1
In [50]: df.loc[:,'D'] = np.array([5] * len(df))

之前操作的结果

1
2
3
4
5
6
7
8
9
In [51]: df
Out[51]:
A B C D F
2013-01-01 0.000000 0.000000 -1.509059 5 NaN
2013-01-02 1.212112 -0.173215 0.119209 5 1.0
2013-01-03 -0.861849 -2.104569 -0.494929 5 2.0
2013-01-04 0.721555 -0.706771 -1.039575 5 3.0
2013-01-05 -0.424972 0.567020 0.276232 5 4.0
2013-01-06 -0.673690 0.113648 -1.478427 5 5.0

带有where操作的设值

1
2
3
4
5
6
7
8
9
10
11
12
13
In [52]: df2 = df.copy()

In [53]: df2[df2 > 0] = -df2

In [54]: df2
Out[54]:
A B C D F
2013-01-01 0.000000 0.000000 -1.509059 -5 NaN
2013-01-02 -1.212112 -0.173215 -0.119209 -5 -1.0
2013-01-03 -0.861849 -2.104569 -0.494929 -5 -2.0
2013-01-04 -0.721555 -0.706771 -1.039575 -5 -3.0
2013-01-05 -0.424972 -0.567020 -0.276232 -5 -4.0
2013-01-06 -0.673690 -0.113648 -1.478427 -5 -5.0

缺失值处理

pandas主要使用np.nan来代表缺失数据。这些值将默认不会包含在计算中,详情请参阅Missing Data section

Reindexing允许您更改/添加/删除指定轴上的索引。这将返回数据的副本。

1
2
3
4
5
6
7
8
9
10
11
In [55]: df1 = df.reindex(index=dates[0:4], columns=list(df.columns) + ['E'])

In [56]: df1.loc[dates[0]:dates[1],'E'] = 1

In [57]: df1
Out[57]:
A B C D F E
2013-01-01 0.000000 0.000000 -1.509059 5 NaN 1.0
2013-01-02 1.212112 -0.173215 0.119209 5 1.0 1.0
2013-01-03 -0.861849 -2.104569 -0.494929 5 2.0 NaN
2013-01-04 0.721555 -0.706771 -1.039575 5 3.0 NaN

删除所有具有缺失值的数据

1
2
3
4
In [58]: df1.dropna(how='any')
Out[58]:
A B C D F E
2013-01-02 1.212112 -0.173215 0.119209 5 1.0 1.0

填充缺失数据

1
2
3
4
5
6
7
In [59]: df1.fillna(value=5)
Out[59]:
A B C D F E
2013-01-01 0.000000 0.000000 -1.509059 5 5.0 1.0
2013-01-02 1.212112 -0.173215 0.119209 5 1.0 1.0
2013-01-03 -0.861849 -2.104569 -0.494929 5 2.0 5.0
2013-01-04 0.721555 -0.706771 -1.039575 5 3.0 5.0

获取值为nan的布尔值掩码

1
2
3
4
5
6
7
In [60]: pd.isna(df1)
Out[60]:
A B C D F E
2013-01-01 False False False False True False
2013-01-02 False False False False False False
2013-01-03 False False False False False True
2013-01-04 False False False False False True

相关操作

详情请参阅Basic section on Binary Ops

统计

一般操作不包括丢失的数据。

执行描述性统计操作(沿数值方向求均值)

1
2
3
4
5
6
7
8
In [61]: df.mean()
Out[61]:
A -0.004474
B -0.383981
C -0.687758
D 5.000000
F 3.000000
dtype: float64

在另一个轴上做相同的操作(沿水平方向求均值)

1
2
3
4
5
6
7
8
9
In [62]: df.mean(1)
Out[62]:
2013-01-01 0.872735
2013-01-02 1.431621
2013-01-03 0.707731
2013-01-04 1.395042
2013-01-05 1.883656
2013-01-06 1.592306
Freq: D, dtype: float64

使用具有不同维度且需要对齐的对象进行操作。另外,pandas会沿指定的尺寸自动广播。

注意:1-np.nan 输出结果为NaN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
In [63]: s = pd.Series([1,3,5,np.nan,6,8], index=dates).shift(2)

In [64]: s
Out[64]:
2013-01-01 NaN
2013-01-02 NaN
2013-01-03 1.0
2013-01-04 3.0
2013-01-05 5.0
2013-01-06 NaN
Freq: D, dtype: float64

In [65]: df.sub(s, axis='index')
Out[65]:
A B C D F
2013-01-01 NaN NaN NaN NaN NaN
2013-01-02 NaN NaN NaN NaN NaN
2013-01-03 -1.861849 -3.104569 -1.494929 4.0 1.0
2013-01-04 -2.278445 -3.706771 -4.039575 2.0 0.0
2013-01-05 -5.424972 -4.432980 -4.723768 0.0 -1.0
2013-01-06 NaN NaN NaN NaN NaN

应用

将函数应用于数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 累加操作
In [66]: df.apply(np.cumsum)
Out[66]:
A B C D F
2013-01-01 0.000000 0.000000 -1.509059 5 NaN
2013-01-02 1.212112 -0.173215 -1.389850 10 1.0
2013-01-03 0.350263 -2.277784 -1.884779 15 3.0
2013-01-04 1.071818 -2.984555 -2.924354 20 6.0
2013-01-05 0.646846 -2.417535 -2.648122 25 10.0
2013-01-06 -0.026844 -2.303886 -4.126549 30 15.0

In [67]: df.apply(lambda x: x.max() - x.min())
Out[67]:
A 2.073961
B 2.671590
C 1.785291
D 0.000000
F 4.000000
dtype: float64

直方图

详情请参阅Histogramming and Discretization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
In [68]: s = pd.Series(np.random.randint(0, 7, size=10))

In [69]: s
Out[69]:
0 4
1 2
2 1
3 2
4 6
5 4
6 4
7 6
8 4
9 4
dtype: int64

In [70]: s.value_counts()
Out[70]:
4 5
6 2
2 2
1 1
dtype: int64

字符串方法

Series对象在其str属性中配备了一组字符串处理方法,可以很容易的应用到数组中的每个元素,如下段代码所示。更多详情请参考:Vectorized String Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [71]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])

In [72]: s.str.lower()
Out[72]:
0 a
1 b
2 c
3 aaba
4 baca
5 NaN
6 caba
7 dog
8 cat
dtype: object

合并

Concat

Pandas提供了大量的方法能够轻松的对Series,DataFrame和Panel对象进行各种符合各种逻辑关系的合并操作。

具体请参阅:Merging section

通过使用concat()来将pandas对象链接起来:

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
In [73]: df = pd.DataFrame(np.random.randn(10, 4))

In [74]: df
Out[74]:
0 1 2 3
0 -0.548702 1.467327 -1.015962 -0.483075
1 1.637550 -1.217659 -0.291519 -1.745505
2 -0.263952 0.991460 -0.919069 0.266046
3 -0.709661 1.669052 1.037882 -1.705775
4 -0.919854 -0.042379 1.247642 -0.009920
5 0.290213 0.495767 0.362949 1.548106
6 -1.131345 -0.089329 0.337863 -0.945867
7 -0.932132 1.956030 0.017587 -0.016692
8 -0.575247 0.254161 -1.143704 0.215897
9 1.193555 -0.077118 -0.408530 -0.862495

# break it into pieces
In [75]: pieces = [df[:3], df[3:7], df[7:]]

In [76]: pd.concat(pieces)
Out[76]:
0 1 2 3
0 -0.548702 1.467327 -1.015962 -0.483075
1 1.637550 -1.217659 -0.291519 -1.745505
2 -0.263952 0.991460 -0.919069 0.266046
3 -0.709661 1.669052 1.037882 -1.705775
4 -0.919854 -0.042379 1.247642 -0.009920
5 0.290213 0.495767 0.362949 1.548106
6 -1.131345 -0.089329 0.337863 -0.945867
7 -0.932132 1.956030 0.017587 -0.016692
8 -0.575247 0.254161 -1.143704 0.215897
9 1.193555 -0.077118 -0.408530 -0.862495

Join

类似于SQL类型的合并

具体请参阅:Database style joining

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
In [77]: left = pd.DataFrame({'key': ['foo', 'foo'], 'lval': [1, 2]})

In [78]: right = pd.DataFrame({'key': ['foo', 'foo'], 'rval': [4, 5]})

In [79]: left
Out[79]:
key lval
0 foo 1
1 foo 2

In [80]: right
Out[80]:
key rval
0 foo 4
1 foo 5

In [81]: pd.merge(left, right, on='key')
Out[81]:
key lval rval
0 foo 1 4
1 foo 1 5
2 foo 2 4
3 foo 2 5

另一个可以给出的例子是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
In [82]: left = pd.DataFrame({'key': ['foo', 'bar'], 'lval': [1, 2]})

In [83]: right = pd.DataFrame({'key': ['foo', 'bar'], 'rval': [4, 5]})

In [84]: left
Out[84]:
key lval
0 foo 1
1 bar 2

In [85]: right
Out[85]:
key rval
0 foo 4
1 bar 5

In [86]: pd.merge(left, right, on='key')
Out[86]:
key lval rval
0 foo 1 4
1 bar 2 5

Append

将一行连接到一个DataFrame上。

详情请参阅Appending

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
In [87]: df = pd.DataFrame(np.random.randn(8, 4), columns=['A','B','C','D'])

In [88]: df
Out[88]:
A B C D
0 1.346061 1.511763 1.627081 -0.990582
1 -0.441652 1.211526 0.268520 0.024580
2 -1.577585 0.396823 -0.105381 -0.532532
3 1.453749 1.208843 -0.080952 -0.264610
4 -0.727965 -0.589346 0.339969 -0.693205
5 -0.339355 0.593616 0.884345 1.591431
6 0.141809 0.220390 0.435589 0.192451
7 -0.096701 0.803351 1.715071 -0.708758

In [89]: s = df.iloc[3]

In [90]: df.append(s, ignore_index=True)
Out[90]:
A B C D
0 1.346061 1.511763 1.627081 -0.990582
1 -0.441652 1.211526 0.268520 0.024580
2 -1.577585 0.396823 -0.105381 -0.532532
3 1.453749 1.208843 -0.080952 -0.264610
4 -0.727965 -0.589346 0.339969 -0.693205
5 -0.339355 0.593616 0.884345 1.591431
6 0.141809 0.220390 0.435589 0.192451
7 -0.096701 0.803351 1.715071 -0.708758
8 1.453749 1.208843 -0.080952 -0.264610

分组(Grouping)

对于”group by”操作,我们通常是指以下一个或多个操作步骤:

  • (Splitting)按照一些规则将数据分为不同的组;
  • (Applying)对于每组数据分别执行一个函数;
  • (Combining)将结果组合到一个数据结构中;

详情请参阅Grouping section

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [91]: df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
....: 'foo', 'bar', 'foo', 'foo'],
....: 'B' : ['one', 'one', 'two', 'three',
....: 'two', 'two', 'one', 'three'],
....: 'C' : np.random.randn(8),
....: 'D' : np.random.randn(8)})
....:

In [92]: df
Out[92]:
A B C D
0 foo one -1.202872 -0.055224
1 bar one -1.814470 2.395985
2 foo two 1.018601 1.552825
3 bar three -0.595447 0.166599
4 foo two 1.395433 0.047609
5 bar two -0.392670 -0.136473
6 foo one 0.007207 -0.561757
7 foo three 1.928123 -1.623033

分组,然后将函数总和sum应用于结果组。

1
2
3
4
5
6
In [93]: df.groupby('A').sum()
Out[93]:
C D
A
bar -2.802588 2.42611
foo 3.146492 -0.63958

按多列分组会形成一个分层索引,然后我们应用这个函数。

1
2
3
4
5
6
7
8
9
10
In [94]: df.groupby(['A','B']).sum()
Out[94]:
C D
A B
bar one -1.814470 2.395985
three -0.595447 0.166599
two -0.392670 -0.136473
foo one -1.195665 -0.616981
three 1.928123 -1.623033
two 2.414034 1.600434

Reshaping

详情请参阅Hierarchical IndexingReshaping

Stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [95]: tuples = list(zip(*[['bar', 'bar', 'baz', 'baz',
....: 'foo', 'foo', 'qux', 'qux'],
....: ['one', 'two', 'one', 'two',
....: 'one', 'two', 'one', 'two']]))
....:

In [96]: index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])

In [97]: df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B'])

In [98]: df2 = df[:4]

In [99]: df2
Out[99]:
A B
first second
bar one 0.029399 -0.542108
two 0.282696 -0.087302
baz one -1.575170 1.771208
two 0.816482 1.100230

stack()方法“压缩”了DataFrame列中的级别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [100]: stacked = df2.stack()

In [101]: stacked
Out[101]:
first second
bar one A 0.029399
B -0.542108
two A 0.282696
B -0.087302
baz one A -1.575170
B 1.771208
two A 0.816482
B 1.100230
dtype: float64

对于“堆叠的(stacked)”DataFrame或Series(以MultiIndex为索引),stack()的反向操作是unstack(),默认情况下,它将卸载最后一层

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
In [102]: stacked.unstack()
Out[102]:
A B
first second
bar one 0.029399 -0.542108
two 0.282696 -0.087302
baz one -1.575170 1.771208
two 0.816482 1.100230

In [103]: stacked.unstack(1)
Out[103]:
second one two
first
bar A 0.029399 0.282696
B -0.542108 -0.087302
baz A -1.575170 0.816482
B 1.771208 1.100230

In [104]: stacked.unstack(0)
Out[104]:
first bar baz
second
one A 0.029399 -1.575170
B -0.542108 1.771208
two A 0.282696 0.816482
B -0.087302 1.100230

数据透视表

详情请参阅Pivot Tables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
In [105]: df = pd.DataFrame({'A' : ['one', 'one', 'two', 'three'] * 3,
.....: 'B' : ['A', 'B', 'C'] * 4,
.....: 'C' : ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 2,
.....: 'D' : np.random.randn(12),
.....: 'E' : np.random.randn(12)})
.....:

In [106]: df
Out[106]:
A B C D E
0 one A foo 1.418757 -0.179666
1 one B foo -1.879024 1.291836
2 two C foo 0.536826 -0.009614
3 three A bar 1.006160 0.392149
4 one B bar -0.029716 0.264599
5 one C bar -1.146178 -0.057409
6 two A foo 0.100900 -1.425638
7 three B foo -1.035018 1.024098
8 one C foo 0.314665 -0.106062
9 one A bar -0.773723 1.824375
10 two B bar -1.170653 0.595974
11 three C bar 0.648740 1.167115

我们可以很容易地从这些数据生成数据透视表:

1
2
3
4
5
6
7
8
9
10
11
12
13
In [107]: pd.pivot_table(df, values='D', index=['A', 'B'], columns=['C'])
Out[107]:
C bar foo
A B
one A -0.773723 1.418757
B -0.029716 -1.879024
C -1.146178 0.314665
three A 1.006160 NaN
B NaN -1.035018
C 0.648740 NaN
two A NaN 0.100900
B -1.170653 NaN
C NaN 0.536826

时间序列

Pandas在对频率转换进行重新采样时拥有简单、强大且高效的功能(如将按秒采样的数据转换为按5分钟为单位进行采样的数据)。这种操作在金融领域非常常见。

详情请参阅Time Series section

1
2
3
4
5
6
7
8
In [108]: rng = pd.date_range('1/1/2012', periods=100, freq='S')

In [109]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)

In [110]: ts.resample('5Min').sum()
Out[110]:
2012-01-01 25083
Freq: 5T, dtype: int64

时区表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
In [111]: rng = pd.date_range('3/6/2012 00:00', periods=5, freq='D')

In [112]: ts = pd.Series(np.random.randn(len(rng)), rng)

In [113]: ts
Out[113]:
2012-03-06 0.464000
2012-03-07 0.227371
2012-03-08 -0.496922
2012-03-09 0.306389
2012-03-10 -2.290613
Freq: D, dtype: float64

In [114]: ts_utc = ts.tz_localize('UTC')

In [115]: ts_utc
Out[115]:
2012-03-06 00:00:00+00:00 0.464000
2012-03-07 00:00:00+00:00 0.227371
2012-03-08 00:00:00+00:00 -0.496922
2012-03-09 00:00:00+00:00 0.306389
2012-03-10 00:00:00+00:00 -2.290613
Freq: D, dtype: float64

转换成其他时区

1
2
3
4
5
6
7
8
In [116]: ts_utc.tz_convert('US/Eastern')
Out[116]:
2012-03-05 19:00:00-05:00 0.464000
2012-03-06 19:00:00-05:00 0.227371
2012-03-07 19:00:00-05:00 -0.496922
2012-03-08 19:00:00-05:00 0.306389
2012-03-09 19:00:00-05:00 -2.290613
Freq: D, dtype: float64

在时间跨度表示之间进行转换

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
In [117]: rng = pd.date_range('1/1/2012', periods=5, freq='M')

In [118]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [119]: ts
Out[119]:
2012-01-31 -1.134623
2012-02-29 -1.561819
2012-03-31 -0.260838
2012-04-30 0.281957
2012-05-31 1.523962
Freq: M, dtype: float64

In [120]: ps = ts.to_period()

In [121]: ps
Out[121]:
2012-01 -1.134623
2012-02 -1.561819
2012-03 -0.260838
2012-04 0.281957
2012-05 1.523962
Freq: M, dtype: float64

In [122]: ps.to_timestamp()
Out[122]:
2012-01-01 -1.134623
2012-02-01 -1.561819
2012-03-01 -0.260838
2012-04-01 0.281957
2012-05-01 1.523962
Freq: MS, dtype: float64

周期和时间戳之间的转换可以使用一些方便的算术功能。在下面的例子中,我们将季度结束时间从11月份转换为季末结束时的上午9点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [123]: prng = pd.period_range('1990Q1', '2000Q4', freq='Q-NOV')

In [124]: ts = pd.Series(np.random.randn(len(prng)), prng)

In [125]: ts.index = (prng.asfreq('M', 'e') + 1).asfreq('H', 's') + 9

In [126]: ts.head()
Out[126]:
1990-03-01 09:00 -0.902937
1990-06-01 09:00 0.068159
1990-09-01 09:00 -0.057873
1990-12-01 09:00 -0.368204
1991-03-01 09:00 -1.144073
Freq: H, dtype: float64

Categorical

pandas可以在DataFrame中引入categorical数据。详情请参阅categorical introductionAPI documentation

1
In [127]: df = pd.DataFrame({"id":[1,2,3,4,5,6], "raw_grade":['a', 'b', 'b', 'a', 'a', 'e']})

将原始的grade转换为Categorical数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
In [128]: df["grade"] = df["raw_grade"].astype("category")

In [129]: df["grade"]
Out[129]:
0 a
1 b
2 b
3 a
4 a
5 e
Name: grade, dtype: category
Categories (3, object): [a, b, e]

将类别重命名为更有意义的名称

1
In [130]: df["grade"].cat.categories = ["very good", "good", "very bad"]

重新排列类别并同时添加缺少的类别(Series .cat下的方法默认返回一个新的系列)。

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

In [131]: df["grade"] = df["grade"].cat.set_categories(["very bad", "bad", "medium", "good", "very good"])

In [132]: df["grade"]
Out[132]:
0 very good
1 good
2 good
3 very good
4 very good
5 very bad
Name: grade, dtype: category
Categories (5, object): [very bad, bad, medium, good, very good]

排序是按类别排序的,而不是词汇顺序。

1
2
3
4
5
6
7
8
9
In [133]: df.sort_values(by="grade")
Out[133]:
id raw_grade grade
5 6 e very bad
1 2 b good
2 3 b good
0 1 a very good
3 4 a very good
4 5 a very good

画图

Plotting文档

1
2
3
4
5
6
In [135]: ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000))

In [136]: ts = ts.cumsum()

In [137]: ts.plot()
Out[137]: <matplotlib.axes._subplots.AxesSubplot at 0x1122ad630>

在DataFrame上,plot()方便绘制所有带标签的列:

1
2
3
4
5
6
7
8
In [138]: df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index,
.....: columns=['A', 'B', 'C', 'D'])
.....:

In [139]: df = df.cumsum()

In [140]: plt.figure(); df.plot(); plt.legend(loc='best')
Out[140]: <matplotlib.legend.Legend at 0x115033cf8>

导入和保存数据

CSV

Writing to a csv file

1
In [141]: df.to_csv('foo.csv')

Reading from a csv file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [142]: pd.read_csv('foo.csv')
Out[142]:
Unnamed: 0 A B C D
0 2000-01-01 0.266457 -0.399641 -0.219582 1.186860
1 2000-01-02 -1.170732 -0.345873 1.653061 -0.282953
2 2000-01-03 -1.734933 0.530468 2.060811 -0.515536
3 2000-01-04 -1.555121 1.452620 0.239859 -1.156896
4 2000-01-05 0.578117 0.511371 0.103552 -2.428202
5 2000-01-06 0.478344 0.449933 -0.741620 -1.962409
6 2000-01-07 1.235339 -0.091757 -1.543861 -1.084753
.. ... ... ... ... ...
993 2002-09-20 -10.628548 -9.153563 -7.883146 28.313940
994 2002-09-21 -10.390377 -8.727491 -6.399645 30.914107
995 2002-09-22 -8.985362 -8.485624 -4.669462 31.367740
996 2002-09-23 -9.558560 -8.781216 -4.499815 30.518439
997 2002-09-24 -9.902058 -9.340490 -4.386639 30.105593
998 2002-09-25 -10.216020 -9.480682 -3.933802 29.758560
999 2002-09-26 -11.856774 -10.671012 -3.216025 29.369368

[1000 rows x 5 columns]

HDF5

HDFStores读取和写入数据

写入HDF5存储:

1
In [143]: df.to_hdf('foo.h5','df')

从HDF5存储中读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [144]: pd.read_hdf('foo.h5','df')
Out[144]:
A B C D
2000-01-01 0.266457 -0.399641 -0.219582 1.186860
2000-01-02 -1.170732 -0.345873 1.653061 -0.282953
2000-01-03 -1.734933 0.530468 2.060811 -0.515536
2000-01-04 -1.555121 1.452620 0.239859 -1.156896
2000-01-05 0.578117 0.511371 0.103552 -2.428202
2000-01-06 0.478344 0.449933 -0.741620 -1.962409
2000-01-07 1.235339 -0.091757 -1.543861 -1.084753
... ... ... ... ...
2002-09-20 -10.628548 -9.153563 -7.883146 28.313940
2002-09-21 -10.390377 -8.727491 -6.399645 30.914107
2002-09-22 -8.985362 -8.485624 -4.669462 31.367740
2002-09-23 -9.558560 -8.781216 -4.499815 30.518439
2002-09-24 -9.902058 -9.340490 -4.386639 30.105593
2002-09-25 -10.216020 -9.480682 -3.933802 29.758560
2002-09-26 -11.856774 -10.671012 -3.216025 29.369368

[1000 rows x 4 columns]

Excel

MS Excel中读取和写入数据

写入一个excel文件

1
In [145]: df.to_excel('foo.xlsx', sheet_name='Sheet1')

读取一个excel文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [146]: pd.read_excel('foo.xlsx', 'Sheet1', index_col=None, na_values=['NA'])
Out[146]:
A B C D
2000-01-01 0.266457 -0.399641 -0.219582 1.186860
2000-01-02 -1.170732 -0.345873 1.653061 -0.282953
2000-01-03 -1.734933 0.530468 2.060811 -0.515536
2000-01-04 -1.555121 1.452620 0.239859 -1.156896
2000-01-05 0.578117 0.511371 0.103552 -2.428202
2000-01-06 0.478344 0.449933 -0.741620 -1.962409
2000-01-07 1.235339 -0.091757 -1.543861 -1.084753
... ... ... ... ...
2002-09-20 -10.628548 -9.153563 -7.883146 28.313940
2002-09-21 -10.390377 -8.727491 -6.399645 30.914107
2002-09-22 -8.985362 -8.485624 -4.669462 31.367740
2002-09-23 -9.558560 -8.781216 -4.499815 30.518439
2002-09-24 -9.902058 -9.340490 -4.386639 30.105593
2002-09-25 -10.216020 -9.480682 -3.933802 29.758560
2002-09-26 -11.856774 -10.671012 -3.216025 29.369368

[1000 rows x 4 columns]

陷阱

如果你正在尝试一个操作,你会看到一个异常:

1
2
3
4
5
>>> if pd.Series([False, True, False]):
print("I was true")
Traceback
...
ValueError: The truth value of an array is ambiguous. Use a.empty, a.any() or a.all().

请参阅Comparisons以获取解释以及如何处理。

同时请参阅Gotchas

原创文章,转载请注明出处

神经网络==多层级组织架构的公司

假设有一家公司,这家公司的组织架构是下面这种多层级的结构:

公司每天接待一批固定数量的用户,这些用户会将自己的数据告诉给公司,公司做的事情就是通过每个用户上报的数据来推测当前用户群体整体所表现出来的状态。

公司中直接与用户打交道的只有基层业务员,小部门经理只与基层业务员打交道,大部门经理只与小部门经理打交道…依此类推,层层递进,直到CEO这一层。

并且其中每一个基层业务员会了解每个用户的数据,每个小部门经理也会了解每个基层业务员输出的情况,…,依次类推,直到CEO。而CEO需要了解的是每个副总经理输出的情况。

公司拿到一批用户的数据之后,首先交给基层业务员。每个基层业务员看到了每个用户的数据之后,都会针对每一个用户出一份数据分析报表(由于每个人的观点不同,所以每个人得出的报表都不一样);然后每个小部门经理也会分别去看每个业务员输出的报表,然后自己再输出一个针对每一个基层业务员输出的数据的分析报表;同样,每个大部门经理也会去这样看每个小部门经理输出的报表,然后出一份针对每一个小部门经理输出的数据的分析报表;…;以此类推,最终CEO会输出一个针对每一个副总经理输出的数据的分析报表,这份报表里就是公司当前对用户状态的理解。

那么公司对用户状态把握到底准确不准确呢?这需要一个衡量标准。

所以我们需要对公司进行考核,将一部分已知状态的用户数据给到公司,看公司是否能足够准确的预测出这个状态。换句话说,就是CEO最后输出的这份报表,与用户的真实状态之间相差有多大。

如果CEO发现自己的预测和真实情况偏差很大,它会带头思考自己工作上到底哪里做的不够好导致最终的判断失误,以及自己需要如何调整状态才能使公司表现更好,然后号召副总经理反思并调整状态。副总经理反思调整之后,会号召他的直属下级部门反思并调整状态。以此类推,直到基层业务员。但其实每个人都不能完全保证调整的状态是否可靠,所以大家就比较保守的稍微调整一下自己的工作状态。

当全公司员工调整状态完成之后,在面对新的用户数据,看是否能更加准确的预测用户群体的状态。如果发现上面的调整确实有效,那么继续按照上面的方式调整:由CEO再次依次号召下面的员工来调整工作状态,调整之后继续面对新的用户。重复执行上面的操作若干次之后,直到公司的预测效果趋于稳定为止(和真实状态对比之后,差值基本不再变化了)。

此时的公司就相当于经历了若干次碰壁,并且若干次全员反思、调整状态之后,各个员工都成为了精兵强将了,对于用户数据的把握也更加准确了。

现在,我们回头看看公司接待的客户。假设公司每天共接待1024位用户,每个用户都举着一个纯色的卡片,卡片颜色是灰度值介于0到255之间的某个颜色。

将这些用户按顺序排在32×32的平面上,每个用户占一格。当他们将手里的纯色卡片高举并拼凑起来之后,我们会看到一个写有数字的图片。

公司做的事情就是每天接待这1024个用户,他们会告诉公司自己手里卡片的灰度值,但不会告诉公司卡片拼起来的图片是什么,然后公司经过层层分析来得出当前所有用户卡片拼起来的图像是什么。

这就是一个用户识别手写数字的DNN模型的形象比喻。


几个问题的思考

公司员工数量一定的情况下,组织架构是越扁平越好,还是层级越多越好?

扁平化的架构带来的好处是快速直接触达用户,但最终的准确率会比较低;多层级的架构带来的好处是更合理的分工,但会带来沟通和管理上的开销和数据损失。

所以如何设置公司层级是一门学问。

每个员工是如何生成报表的?

每个员工根据自己对每个数据的重要程度的看法,计算出自己对每个数据的看法。然后在经过一层加工处理之后输出报表。

公司招聘员工的时候,应该招聘类似的人群,还是招聘差异化的人群?

应该招聘差异化大的人群,这样每个人能够产生对数据的不同看法。如果公司大部分人背景相似,那么他们对待同一类问题,产生的看法也都相似,没有多样化的观点,也容易导致公司做出错误判断。


带入术语

  • 神经网络 -> 多层级结构的公司
  • 神经网络架构 -> 公司组织架构
  • 神经元 -> 每一位员工
  • 损失函数/代价函数 -> 公司最终预测结果 - 真实结果
  • 输入数据源 -> 每天所有用户的卡片灰度值向量
  • 输入数据标签值 -> 所有用户卡片拼凑起来的数字图像的真实数值
  • 反向传播 -> 由CEO牵头,依次带领全公司员工反思
  • 正向传递 -> 带入每天的用户数据,层层递进,输出最终预测结果
  • 激活函数 -> 每个员工对数据的加工
  • 参数 -> 每个员工对数据的主观看法
  • 随机初始化参数 -> 招聘差异化人群
  • 输入层 -> 用户层
  • 隐藏层 -> 除了CEO之外的所有员工层级
  • 输出层 -> CEO层
  • 梯度下降 -> 公司朝着缩小预测错误程度的方向全员反思调整状态的过程
  • 学习率 -> 每次调整状态的程度α
  • 训练/学习 -> 带入大量已知状态的用户数据来根据公司的预测结果调整全员状态的过程

总结

神经网络是一个灵活的结构,当带入图片像素值以及图片标签数据时,它训练的就是一个图片识别模型;当带入的数据是邮件特征数据,以及邮件是否为垃圾邮件的类别数据时,它可能就是一个垃圾邮件识别模型。

这里的类比并不严谨,准确的定义还需要参考标准定义。不过通过形象化的类比,可以使我们对神经网络建立起系统化的认知。

当你训练一个模型时,您可以使用variables来保存和更新参数。variables是包含张量的内存缓冲区。variables必须明确地被初始化,并在训练期间和之后将其保存到磁盘。在之后您可以恢复保存的值,以运行或分析模型。

本文档引用了以下TensorFlow类。请参阅其参考手册的链接,了解其API的完整说明:

创建

当您创建一个Variable时,您将Tensor作为其初始值传递给Variable()构造函数。TensorFlow提供了一个操作的集合,它们产生经常用于从常量或随机初始化的张量。

请注意,所有这些操作都需要您指定张量的形状。该形状自动变为变量的形状。变量通常具有固定的形状,但是TensorFlow提供了重新变换变量的高级机制。

程序员指南

这一部分文档将深入到TensorFlow的代码细节。这一节由以下几个指南开始,每一个指南都介绍了TensorFlow的一个特定的方面:

以下指南适用于对复杂模型的多天训练:

TensorFlow提供了一个名叫tfdbg的调试器,它的文档见下面两个指南:

MetaGraph由计算图及其相关元数据组成。MetaGraph包含持续训练,执行评估或在先前训练过的图表上运行推断所需的信息。以下指南是MetaGraph对象的详细说明:

SavedModel是Tensorflow模型的通用序列化格式。TensorFlow提供SavedModel CLI(命令行界面)作为在SavedModel中检查和执行MetaGraph的工具。以下指南中记录了详细的用法和示例:

要了解TensorFlow版本控制方案,请参阅以下两个指南:

结束本部分有关TensorFlow编程的常见问题: