LittleQ

爱好:写代码

记录一下关于学习Django的一些细节,主要还是网上的一些教程实在是太坑了,可能是早期的版本,所以各种报错,所以最后都是参考了官方的原版教程,以及结合着其他博客慢慢折腾出来的,这里我会尽量把作为一个新手的常用问题记录下来。

环境准备

OS:Ubuntu 14.04 64bits
Python: 2.7.6
Django:1.10.dev20151230230702

安装Django

方法很多,只讲我的方法

1
2
3
cd /usr/dev
git clone git://github.com/django/django.git
sudo pip install -e django/

**注意:**如果没有装pip,可以直接在终端里装:

1
sudo apt-get install python-pip

最终目录结构看起来就是下面这样:

1
2
3
4
5
6
7
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py

关于这些文件的用途,大概介绍一下,开始不必过多纠结这个,后面用到了会详细讲:

  1. 外层的mysite/是工程的根目录,这个名字对于Django来说无所谓,你可以随便改个名字。
  2. manage.py:管家,启动服务器,数据库之类的都需要通过这个来完成。
  3. mysite/__init__.py:这个空的文件只是为了标示这个文件夹是个Python包。
  4. mysite/settings.py:整个工程的配置文件。
  5. mysite/urls.py:整个Django工程url映射。
  6. mysite/wsgi.py:一个供调试用的小型web服务器,类似Apache,tomcat。

启动服务

在外层的mysite目录下,在终端中执行下面的命令:

1
python manage.py runserver

此时就可以在控制台看到成功启动服务器的信息,浏览器访问http://127.0.0.1:8000/,能访问看到对应的欢迎信息则说明成功了。
再多说一点,如果不指定端口和IP,则默认的就是127.0.0.18000.着意味着你只能是在本地访问这个网址,如果要局域网里其他的机器可以访问你的这个网址,你需要像下面这样启动:

1
python manage.py runserver 0.0.0.0:8000

这样服务器会监听所有的公共IP地址,你可以通过局域网其他IP地址访问这台机器的ip地址加端口号来访问对应的网址。

创建一个应用

首先看看应用和工程有什么区别?我们可以这样理解,一个应用就是完成了一些功能的一个Web程序,一个工程就是由很多应用,或者说模块组成的,各个应用完成自己的功能,组成了功能强大的工程,下面我们来创建一个应用,还是到最外层的mysite下:

1
python manage.py startapp polls

刚刚创建的polls文件夹的目录就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
polls|master⚡ ⇒ tree
.
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
├── urls.py
├── views.py

来写我们的第一个视图,编辑polls/views.py文件,代码如下:

1
2
3
4
5
6
from django.http import HttpResponse


# Create your views here.
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")

这个是一个最简单的视图了,为了访问到这个视图,我们需要创建一个URL映射,编辑polls/urls.py文件,最终文件看上去像下面这样:

1
2
3
4
5
6
7
8
9
#!/usr/bin/env python
# coding=utf-8

from django.conf.urls import url
from . import views

urlpatterns = [
url(r'^$', views.index, name='index'),
]

光这样还不行,这个只是把访问这个应用时,内部如何处理映射了,但是最外层如何访问到这个内层应用,还需要在mysite中配置一个URL映射,修改mysite/urls.py文件:

1
2
3
4
5
6
7
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
url(r'^polls/', include('polls.urls')),
url(r'^admin/', admin.site.urls),
]

记得要导入include模块。
**注意:**什么时候需要使用include模块?当你的URL匹配中包含另外的URL匹配映射时,你需要用include(),对于我们的例子,当在处理127.0.0.1:8000/polls/这个链接时,需要用到polls/urls.py里面的映射,所以我们要用include()将其他的匹配规则包含进来。
一切都完成了之后,我们来启动工程:

1
python manage.py runserver

同样,浏览器访问http://127.0.0.1:8000/polls/,如果看到输出了:

Hello, world. You’re at the polls index.

则说明我们的第一个应用完成了。

经常要处理数据,自带的那个LibreOffice看看数据还行,但是要自己统计,我还是不太会用,所以就装了个WPS for Linux.之前也装过一次,但是装完了启动不了,今天要做统计,所以就再装了一下.

首先要下载安装包wps-office_10.1.0.5444~a20_amd64.deb

直接点开就可以安装,但是正常情况下会安装失败,缺少依赖啥的,所以要装依赖库libc6-i386:

1
2
$ sudo apt-get update
$ sudo apt-get install libc6-i386

安装成功之后再重新安装WPS的安装包,装好了就可以使用了,记住,刚开始启动可能会有个警告报错,缺少字体,下载对应的字体就可以了,当然不下载也是可以使用的,没有什么多大的区别.

首先,这个东西在其他的面向对象的语言里好像没有,所以第一次碰到难免会有些不好理解,网上看到的教程,做个笔记整理以后不懂再来看看.
要讲yield就必须先讲讲Python中的迭代器(iterator)和生成器(generator)

迭代器(iterator)

在Python中,for循环比其他的语言中的都要强大,可以用来遍历Python中的任何类型,包括列表,元祖等,其实根本的判断原则就是:

for 循环可以用于任何 可迭代对象

这个含义就是迭代器,迭代器是一个实现了迭代器协议的对象,Python中的迭代器协议规定了必须有next()方法,调用这个方法会前进到下一个结果,当迭代器到达末尾的时候,会触发StopIteration.具有这些特性的对象在Python中都可以用for循环或其他遍历工具迭代,迭代的时候,每次都会调用next()方法,并且一旦捕捉到StopIteration异常就会停止迭代.
使用迭代器除了在写代码的时候看上去更简洁,优雅,更大的一个好处就是:每次只从对象中读取一条数据,不会造成过大的开销.
举个读文件的例子,一般我们会经常用到逐行读取一个文件的内容,利用readlines()方法,可以这么写:

1
2
for line in open('test.txt','r').readlines():
print line

看上去代码确实很优雅,简洁,但其实这并不是最好的方法,因为这其实也是一次把文件加载到了内存,然后再逐行遍历打印的,内存卡销很大,碰上一个很大的文件,程序有可能会崩溃.
这个时候我们可以利用file迭代器,优化之后的代码:

1
2
for line in open('test.txt','r'):
print line

虽然代码改动很小,甚至还去掉了一些代码,但是这个是运行速度最快的读文件的一种方法,没有显示的去读文件,而是利用迭代器每次读取下一行.

生成器(generator)

生成器函数在Python中与迭代器协议的概念联系在一起.简而言之,包含yield语句的函数会被特地编译成生成器.于是当我们调用这类函数的时候,会返回一个生成器对象,这个对象支持迭代器接口.而且就算函数有个return,它的作用也是用来yield产生值的.也就是不像一般的函数会生成值后退出,生成器函数在生成值后会自动挂起并暂停它们的执行状态,本地变量会保存状态信息,当函数恢复时将再度有效:

1
2
3
4
5
6
7
8
def g(n):
for i in range(n):
yield i **2

for i in g(5):
print i, ':',

0:1:4:9:16:

这么看好像也不太容易懂,我们用迭代器的next()方法来看看具体的运行原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> t = g(5)
>>> t.next()
0
>>> t.next()
1
>>> t.next()
4
>>> t.next()
9
>>> t.next()
16
>>> t.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

在运行完5次next()之后,生成器抛出了一个StopIteration异常,迭代终止。再来看一个yield的例子,用生成器生成一个Fibonacci数列:

1
2
3
4
5
6
7
8
9
10
11
>>> def fab(max):
... n, a, b = 0, 0, 1
... while n <= max:
... yield b
... a, b = b, a + b
... n += 1
...
>>> for n in fab(5):
... print n,
...
1 1 2 3 5 8

**解释:**在for循环执行时,每次循环都会执行fab函数内部的代码,执行到yield b时,fab函数就返回一个迭代值,下次迭代时,代码从yield b的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到yield.

今天看到了一个用Python校验文件md5的代码,但是本人Python不是很熟,所以里面有很多语法看的不是很懂,大概讲讲吧.
首先要想校验一个文件的md5值在Linux下很方便,命令行就可以校验:

1
2
➜  ~  md5sum 订单数据.xls 
933ed658af57a9aec4d1ba9800fe5460 订单数据.xls

虽然你也可以调用Python来校验文件的md5,但是这样很不方便,也不能保证windows下也可以运行吧,Python中也md5校验的函数,Python 2.5之后推荐使用hashlib来代替md5模块来做校验.

简单的MD5校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python
# coding=utf-8
import hashlib


def md5sum(file_name):
fp = open(file_name, 'rb')
content = fp.read()
fp.close()
m = hashlib.md5(content)
file_md5 = m.hexdigest()

return file_md5


if __name__ == '__main__':
print md5sum('TCPServer.py')

注意:hashlib.md5()返回的是一个对象,要想得到md5值,还需要调用一下hexdigest()方法.还有一个地方需要注意一下,这个校验方法有个很大的缺陷,由于是要校验文件里面的内容,所以每次是把文件读到内存的,试想一下,如果这个文件很大,先不说慢,更有可能程序就直接卡死了.

改进的MD5校验

下面的代码确实写的很牛逼,对于我这种刚学Python的渣渣来说,很多地方都看的不太明白,先把程序贴出来,后面有一些程序中不懂的地方的一些详尽的解释.优化之后的代码:

加密字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def md5hex(word):
"""
MD5加密算法,返回32为小写16进制符号
:param word: 需要加密的字符串
:return: 返回32为小写16进制符号
"""
if isinstance(word, unicode):
word = word.encode('utf-8')
elif not isinstance(word, str):
word = str(word)

m = hashlib.md5()
m.update(word)

return m.hexdigest()

**解释:**查了一下Python里关于strunicode的区别,简单来说就是str类似于byte[],即字节字符串.而unicode类似于char[].说白了就是Python内部存储用unicode,而在和人交互的时候用str,str是字节串,由unicode经过编码(encode)后的字节组成的,所以它们的关系就是unicode.encode()-->str或者str.decode()-->unicode().时刻记住:

unicode才是真正的字符串.

加密文件

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
def md5sum(file_name):
"""
根据给定文件名计算文件MD5值
:param file_name: 文件的路径
:return: 返回文件MD5校验值
"""

def read_chunks(fp):
fp.seek(0)
chunk = fp.read(8 * 1024)
while chunk:
yield chunk
chunk = fp.read(8 * 1024)
else: # 最后要将游标放回文件开头
fp.seek(0)

m = hashlib.md5()
if isinstance(file_name, basestring) \
and os.path.exists(file_name):
with open(file_name, 'rb') as fp:
for chunk in read_chunks(fp):
m.update(chunk)
elif file_name.__class__.name__ in ["StringIO", "cStringIO"] \
or isinstance(file_name, file):
for chunk in read_chunks(file_name):
m.update(chunk)
else:
return ""

return m.hexdigest()

这段程序写的确实很牛逼,我稍微解释一下:

  1. 8~15行:的意思就是每次返回读取的文件的8k的内容,下次从上次读取的地方继续读取,如果读完了,就把游标重新放到文件开头.注意这里面用到了yield,对这个关键字不熟的可以简单把他理解为关键字return.但是它和return不同的地方在于,再次调用函数的时候,上次调用的变量还是有效的,也就是说它不是从文件的开头重新读,而是接着上回读的地方再读8k的内容
  2. 18行:isinstance(file_name, basestring)这个是判断文件名是不是一个basestring类型,basestringstrunicode的父类,这个类是个抽象类,不能被实例化,不过可以被用来判断一个对象是不是字符串.with关键字,简单理解为try/catch就可以了,即使发生了异常,文件也可以正常关闭.
  3. 23行:StringIOcStringIOfile对象非常像,指内存里的文件,例如上传的文件缓存或者已经打开的文件流,那么就不用打开文件,直接读就行

总结

这个程序其实就是每次读取8k的内容,然后更新已经读取的内容的md5值,核心代码:

1
2
for chunk in read_chunks(fp):
m.update(chunk)

如果不明白update()的用法,可以测试一下:

1
2
3
4
5
6
7
8
9
>>> import hashlib
>>> x = hashlib.md5()
>>> x.update('hello, ')
>>> x.update('python')
>>> x.hexdigest()
'fb42758282ecd4646426112d0cbab865'
>>> hashlib.md5('hello, python').hexdigest()
'fb42758282ecd4646426112d0cbab865'
>>>

所以可以看到,update()方法其实并不会丢弃上一次校验的结果,就是在不停的累加,尤其适合处理大文件.

心血来潮想写个tcp的服务端和客户端来向服务器传文件,其中为了让服务器在我正式传文件之前知道我所要传文件的详细信息,我用了struct.pack()函数,给文件名留了256个字节的内容。
这个时候问题就来了,一般我传的文件名称并没有那么长,但是这样名字是可以传过去,但是每次一调用fp = open(file_path, 'wb')就报错:

1
Python 2.7 TypeError: file() argument 1 must be encoded string without NULL bytes, not str

打印出来确实没有问题,是我的文件名,那到底是哪里出问题了?
仔细看报错信息,大概明白了,原来这里有个坑,Python是用C写的,所以Python里的字符串和C里面的char[]数组一样,是以\0结尾的,所以当你的路径里含有这些空值的时候,打开会报错,可以验证一下:

1
2
repr(file_path)
Python/socket/udp/Django-1.8.5.tar.gz\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

看到没有,果然有很多这种值,原来在调用struct.pack()函数的时候,不足的地方会填上这些空的东西,print是不会显示的,但是在打开文件的时候就悲剧了。
解决办法:
直接把这些空的去掉

1
file_path.strip('\0')

最近尝鲜试了一把Atom编辑器,发现在Ubuntu上这个编辑器果然还是刚出来,不是很成熟,中文显示不了,换了中文编码GBKGB18030也不行。网上很多人说换成文泉驿字体就可以了,但是我试了发现,还是乱码。很多人都是抄那一种方法,都没有去试试到底行不行,其实方法没有错,只是不是所有的人都可以这么解决乱码,正确的步骤应该是:

  1. 从菜单中打开Edit->Open your config选项,或者setting views中的font-family选项,把字体设置成你机器上有的中文字体

  2. 怎么看自己机器上的中文字体,你可以在终端输入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fc-list :lang=zh
    /usr/share/fonts/truetype/arphic/uming.ttc: AR PL UMing TW MBE:style=Light
    /usr/share/fonts/truetype/arphic/ukai.ttc: AR PL UKai CN:style=Book
    /usr/share/fonts/truetype/arphic/ukai.ttc: AR PL UKai HK:style=Book
    /usr/share/fonts/simsun.ttf: SimSun,宋体:style=Regular
    /usr/share/fonts/truetype/arphic/ukai.ttc: AR PL UKai TW:style=Book
    /usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf: Droid Sans Fallback:style=Regular
    /usr/share/fonts/truetype/arphic/ukai.ttc: AR PL UKai TW MBE:style=Book
    /usr/share/fonts/truetype/arphic/uming.ttc: AR PL UMing TW:style=Light
    /usr/share/fonts/truetype/arphic/uming.ttc: AR PL UMing CN:style=Light
    /usr/share/fonts/truetype/arphic/uming.ttc: AR PL UMing HK:style=Light

    之前由于IDEA这款IDE有乱码我下载了SimSun字体,所以我就没有按照网上的去设置什么文泉驿字体了,我压根就没有,再设置也是乱码。

  3. 最终配置如下:

    1
    2
    3
    4
    5
    6
    editor:

    invisibles:
    {}
    tabLength: 4
    fontFamily: "SimSun"

经常需要把文件上传到服务器,最开始是用scp命令具体参见Linux 本地和服务器互传文件命令:scp,但是公司登陆服务器需要先登陆跳板机,所以需要先把文件scp到跳板机上,然后再scp到服务器上,很麻烦.所以后来想到了用公司的git服务器,先把文件push到git服务器上,然后再在服务器上直接pull下来,用完了再删掉数据commit更新就行了.这样其实也基本满足了需求,但是还是觉得麻烦,特别是每次还需要进行一系列git操作,还要一直留着这个仓库.
当然,从服务器上下载需要的文件也很简单,不用scp这么几次,直接在服务器上需要下载文件的目录执行:

1
2
# 当前目录为服务器根目录,端口为8000
python -m SimpleHTTPServer 8000

本地机器执行打开浏览器,输入:

1
http://server_ip:8000

就可以直接下载了

正好这几天看了一下python的socket,直接写个小型的服务端和客户端,直接传文件即可.

Socket发送消息

通常对于一个标准的时间,例如2015-12-05 12:34:40,如果想获取它的年月日,我们可以采取取字串的形式,即:

1
2
3
substr(create_time,1,10)	# 取2015-12-05
substr(create_time,1,7) # 取2015-12
substr(create_time,1,4) # 取2015

也确实,对于一般的需求这么处理也够用了,但是有一个问题,如果要按周来统计,这个取字串就无法做到了,这个时候就需要用到mysql内置函数:

1
date_format(date,format)

如果要按星期,天,月份来统计数据,可以这么来写,假设时间字段是create_time.

1
2
3
SELECT date_format(create_time,'%y%u') week,count(id) FROM table GROUP BY week;		# 按星期统计
SELECT date_format(create_time,'%y%m%d') day,count(id) FROM table GROUP BY day; # 按天统计
select date_format(create_time,'%y%m') month,count(id) FROM table GROUP BY month; # 按月统计

详细参数:
根据format字符串格式化代date的值.具体的格式取值有:

format 取值 含义
%M 月名字(January…December)
%W 星期名字(Sunday…Saturday)
%D 有英语前缀的月份的日期(1st, 2nd, 3rd, 等等。)
%Y 年, 数字,4 位(2013,2014,2015)
%y 年, 数字, 2 位(13,14,15)
%a 缩写的星期名字(Sun…Sat)
%d 月份中的天数, 数字(00…31)
%e 月份中的天数, 数字(0…31)
%m 月, 数字(01…12)
%c 月, 数字(1…12)
%b 缩写的月份名字(Jan…Dec)
%j 一年中的天数(001…366)
%H 小时(00…23)
%k 小时(0…23)
%h 小时(01…12)
%I 小时(01…12)
%l 小时(1…12)
%i 分钟, 数字(00…59)
%r 时间,12 小时(hh:mm:ss [AP]M)
%T 时间,24 小时(hh:mm:ss)
%S 秒(00…59)
%s 秒(00…59)
%p AM或PM
%w 一个星期中的天数(0=Sunday…6=Saturday )
%U 星期(0…52), 这里星期天是星期的第一天
%u 星期(0…52), 这里星期一是星期的第一天
%% 一个文字“%”。

简单介绍一下什么是Apriori算法: Apriori算法是一种挖掘关联规则的频繁项集算法,其核心思想是通过候选集生成和情节的向下封闭检测两个阶段来挖掘频繁项集。 Apriori(先验的,推测的)算法应用广泛,可用于消费市场价格分析,猜测顾客的消费习惯;网络安全领域中的入侵检测技术;可用在用于高校管理中,根据挖掘规则可以有效地辅助学校管理部门有针对性的开展贫困助学工作;也可用在移动通信领域中,指导运营商的业务运营和辅助业务提供商的决策制定。

背景知识

关联规则做数据挖掘一般的步骤可以分为两大类:

  1. 依据支持度找出所有频繁项集(频度)
  2. 依据置信度产生关联规则

基本概念

上面提到了两个概念:支持度和置信度。其实数据挖掘里面的概念很多,这里简单介绍几个最基本的术语,后面的变量和集合都会用到。

定义

  1. 资料库(transaction database):存储着二维结构的记录集,定义为D
    **备注:**这个可以理解为数据库里面的交易记录,每一条交易为一条记录,所有这些需要挖掘的数据构成了一个记录集。

  2. 所有项集(items):所有项目的集合,定义为I.
    **备注:**这个可以理解为,把资料库里的每一条记录拆分成最小单位,最后构成的一个集合

  3. 记录(transaction):在资料库的一笔记录。定义为T,T∈D

  4. 项集(itemset):同时出现项的集合。定义为:k-itemset(k项集)
    **备注:**同时出现项的集合意思是所有项集里面的单个项或者多个项组合成的一条记录
    解释:[[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]就是资料库D,里面有4条记录,所有项集I[[1],[2],[3],[4],[5]],有5项;[1,3,4]为其中一条记录T;[2,5]称为2项集,[2,3,5]称为3项集。

  5. 支持度(support):定义为
    $supp(X)=\frac{occur(X)}{count(D)}=P(X)$.
    **备注:**支持度可以简单理解为投票,选举这种,在总人数(资料库),有多少人是选择支持你的,即X出现的次数(频度),即X出现的概率

  6. 置信度(confidence/strength):定义为
    $conf(X->Y)=\frac{supp(X∪Y)}{supp(X)}$
    即下面的意思$P(Y|X)=\frac{P(XY)}{P(X)}$
    **备注:**这个公式可以这么理解,买了X的人有多少人会同时会买Y,这就是XY的推荐度,概率论中的解释就是P(Y|X)=P(XY)/P(X)。看一个例子:
    例子:[支持度:3%,置信度:40%]
    支持度3%:意味着3%顾客同时购买牛奶和面包
    置信度40%:意味着购买牛奶的顾客40%也购买面包

  7. 候选集(Candidate itemset):通过合并得出的项集,有k个元素的候选集记为C[k].

  8. 频繁集(Frequent itemset):支持度>=特定的最小支持度(Minimum Support/minsup)的项集,表示为L[k].
    **注意:**频繁集的子集一定是频繁集,这个也好理解,假设子集都不满足最小支持度,那么与他组合的概率必然是只会比这个概率更小,参见概率论里面的联合概率.

  9. 提升比率(提升度Lift):
    $lift(X->Y)=lift(Y->X)
    =\frac{conf(X->Y)}{supp(Y)}=\frac{conf(Y->X)}{supp(X)}
    =\frac{P(XY)}{P(X)P(Y)}$
    经过关联规则分析后,针对某些人推销(根据某规则)比盲目推销(一般来说是整个数据)的比例,这个比率越高越好,我们称这个为强规则,这个可以理解为:假设买了X的人多半会买Y,那么我们就对买了X的人推销Y.

Apriori原理

Apriori例子

先来个简单的例子,最初这个例子来源于零售商,我们假设有4个商品,编号为0,1,2,3,那么能够被一起购买的商品组合为:
集合{0,1,2,3}中所有可能项集的组合
我们的目标是找到经常一起购买的商品,这里就可以使用集合的支持度来衡量一个组合出现的频率.即某个组合出现的概率占总交易量的比例就是其支持度.这个理解起来很简单,但是计算起来计算量可不小,这里才4种物品,我们需要计算$2^n-1$种情况,正常应用场景绝不可能才4种商品,这个时候就要提到Apriori原理,可以大大减少频繁项集的数目
Apriori原理:如果某个项集是频繁项集,那么它所有的子集也是频繁的.举个例子就是如果{0,1}是频繁的,那么{0},{1}也一定是频繁的,上面介绍过了,这里就不再细说了.正着看没什么用,但是反过来,即一个项集(例如{0},{1})是非频繁项集,则他的超集({0,1})也是非频繁的:
图中给出了所有可能的项集,其中蓝色表示非频繁项集,由于{0,1}非频繁,因此,它们的超集也是非频繁的,他们的支持度可以直接忽略,俗称减枝

使用Apriori返回目录

关联分析的两个目标:发现频繁项集和发现关联规则.首先要找到频繁项集,然后根据频繁项集找到关联规则.
Apriori寻找频繁项集的一般步骤是:

  1. 首先生成所有单个物品的项集列表
  2. 扫描交易记录查看哪些项集满足最小支持度要求,去掉那些不满足的项集
  3. 对剩下的项集进行组合生成包含两个元素的项集
  4. 重复2~3,直到所有项集都被去掉

下面看看Python代码实现:

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
# -*- coding: utf-8 -*-

def loadDataSet():
"""
创建一个用于测试的简单的数据集
如果需要从其他地方加载产生数据,只需要重写这个方法即可
"""
return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

def createC1(dataSet):
"""
构建初始候选项集(Candidate)的列表,即所有候选项集只包含一个元素,
C1是大小为1的所有候选项集的集合
"""
C1 = []
for transaction in dataSet:
for item in transaction:
if [item] not in C1:
C1.append([item])
C1.sort()
return map(frozenset, C1)

def scanD(D, Ck, minSupport):
"""
计算Ck中的项集在数据集合D(记录或者transactions)中的支持度,
返回满足最小支持度的项集的集合,和所有项集支持度信息的字典。
"""
ssCnt = {}
for tid in D:
# 对于每一条transaction
for can in Ck:
# 对于每一个候选项集can,检查是否是transaction的一部分
# 即该候选can是否得到transaction的支持
if can.issubset(tid):
if not ssCnt.has_key(can):
ssCnt[can] = 1
else:
ssCnt[can] += 1
numItems = float(len(D))
retList = []
supportData = {}
for key in ssCnt:
# 每个项集的支持度
support = ssCnt[key] / numItems

# 将满足最小支持度的项集,加入retList
if support >= minSupport:
retList.insert(0, key)

# 汇总支持度数据
supportData[key] = support
return retList, supportData

上面代码中 “frozenset”,是为了冻结集合,使集合由“可变”变为 “不可变”,这样,这些集合就可以作为字典的键值.测试一下上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
if __name__ == '__main__':
# 导入数据集
myDat = loadDataSet()
# 构建第一个候选项集列表C1
C1 = createC1(myDat)

# 构建集合表示的数据集 D
D = map(set, myDat)
# 选择出支持度不小于0.5 的项集作为频繁项集
L, suppData = scanD(D, C1, 0.5)

print u"频繁项集L:", L
print u"所有候选项集的支持度信息:", suppData

看看运行出来的结果:

1
2
频繁项集L: [frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])]
所有候选项集的支持度信息: {frozenset([4]): 0.25, frozenset([5]): 0.75, frozenset([2]): 0.75, frozenset([3]): 0.75, frozenset([1]): 0.5}

可以看出,只有支持度不小于0.5的项集被选中到L中作为频繁项集,根据不同的需求,我们可以设定最小支持度的值,从而得到我们想要的频繁项集.
上面我们找出了只含有一个元素的频繁项集,下面需要整合一下代码,一次选择

经常在服务器上登陆,操作,有些命令一旦执行,就一直在屏幕上输出内容,如果想切换到终端去干其他的事,新手通常是直接Ctrl+c强制删除任务,然后再回来重新执行.其实完全没这个必要,我们可以把任务切换到后台挂起,然后去干我们自己的事情,干完了之后再切回来,继续执行之前没执行完的任务就行了,听上去是不是像操作系统的进程和线程调度,下面是一些常用的Shell作业管理命令.

作业管理

  1. 当前作业放到后台暂停:Ctrl+z

  2. 观察当前前后台作业状态:jobs

    -l 除了列出作业号之外同时列出PID
    -r 列出仅在后台运行(run)的作业
    -s 仅列出暂停的作业

  3. 将后台作业拿到前台处理:fg

    fg %jobnumber (%可有可无)

  4. 让作业在后台运行:bg

    Ctrl+z让当前作业到后台去暂停,bg作业号就可以在后台run

  5. 管理后台作业:kill
    前面讲的都是把作业放到后台,或者切换到前台,要么挂起,要么执行.如果要删除作业或者重启作业,需要给作业发送特定信号
    kill -signal %jobnumber

    -l 列出当前kill能够使用的信号
    signal 表示给后台作业什么指示,用man 7 signal可知
    -1 重新读取一次参数的设置文件,类似 reload
    -2 表示与由键盘输入Ctrl+c同样的作用
    -9 立刻强制删除一个作业
    -15 以正常方式终止一项作业,与-9不一样

应用场合

有些程序没办法通过应用图标运行,只能调用脚本运行,并且一堆输出日志,当你在服务器上使用Vim编辑一个文件时,想去运行一个脚本,可以先保存Vim内容,然后把Vim切换到后台挂起,去执行脚本之后再且回来继续编辑Vim.

0%