From 5984d4c4b3018bb420ed3b524ecaac26f9ef4ab7 Mon Sep 17 00:00:00 2001 From: guozhen3 Date: Mon, 5 Jul 2021 21:51:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BE=8B=E5=AD=90198=E5=B8=B8=E8=A7=81?= =?UTF-8?q?=E7=AE=97=E6=9C=AF=E8=BF=90=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes dev/python-dev.md | 1481 +++++++++++++++++++++++++++++++++++++++++++++ md/198.md | 21 +- 3 files changed, 1500 insertions(+), 2 deletions(-) create mode 100644 .DS_Store create mode 100644 dev/python-dev.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..02ccf120dfabed7540ba6f7888f67a48c1b993af GIT binary patch literal 6148 zcmeHKJ5Iwu5S=9{EYYN-+$-b;OE4$M1yEuHMJ%U*l(^$0oFq9IZ$5mY6)q{FH`2`W z&S%$t#g0cr((0vKiY!E=fhx+onp!ljTFF&-=D?(TEZ5ti-&c844y~O@Cy(;Fd#dUq z(hqIB*$o@yHmA3b!}jiY`%~8s-9&W%TVUBk0I0w5CU~}40+`hSEP8=Ku2|uE+oxxF`nFyj`~|d{Xq* y>f^N6X6OU-OOR{nFxHBJ){3#vR($g-FY7bnC2(}Ka`>$r$R7dag+d1YgMm-qgC=PJ literal 0 HcmV?d00001 diff --git a/dev/python-dev.md b/dev/python-dev.md new file mode 100644 index 00000000..1c5c04b8 --- /dev/null +++ b/dev/python-dev.md @@ -0,0 +1,1481 @@ +### Python 实战 + + +#### 221 自动群发邮件 + +Python自动群发邮件 + +```python +import smtplib +from email import (header) +from email.mime import (text, application, multipart) +import time + +def sender_mail(): + smt_p = smtplib.SMTP() + smt_p.connect(host='smtp.qq.com', port=25) + sender, password = '113097485@qq.com', "**************" + smt_p.login(sender, password) + receiver_addresses, count_num = [ + 'guozhennianhua@163.com', 'xiaoxiazi99@163.com'], 1 + for email_address in receiver_addresses: + try: + msg = multipart.MIMEMultipart() + msg['From'] = "zhenguo" + msg['To'] = email_address + msg['subject'] = header.Header('这是邮件主题通知', 'utf-8') + msg.attach(text.MIMEText( + '这是一封测试邮件,请勿回复本邮件~', 'plain', 'utf-8')) + smt_p.sendmail(sender, email_address, msg.as_string()) + time.sleep(10) + print('第%d次发送给%s' % (count_num, email_address)) + count_num = count_num + 1 + except Exception as e: + print('第%d次给%s发送邮件异常' % (count_num, email_address)) + continue + smt_p.quit() + +sender_mail() +``` + + + +注意: +发送邮箱是qq邮箱,所以要在qq邮箱中设置开启SMTP服务,设置完成时会生成一个授权码,将这个授权码赋值给文中的`password`变量 + +#### 222 二分搜索 + +二分搜索是程序员必备的算法,无论什么场合,都要非常熟练地写出来。 + +小例子描述: +在**有序数组**`arr`中,指定区间`[left,right]`范围内,查找元素`x` +如果不存在,返回`-1` + +二分搜索`binarySearch`实现的主逻辑 + +```python +def binarySearch(arr, left, right, x): + while left <= right: + + mid = int(left + (right - left) / 2); # 找到中间位置。求中点写成(left+right)/2更容易溢出,所以不建议这样写 + + # 检查x是否出现在位置mid + if arr[mid] == x: + print('found %d 在索引位置%d 处' %(x,mid)) + return mid + + # 假如x更大,则不可能出现在左半部分 + elif arr[mid] < x: + left = mid + 1 #搜索区间变为[mid+1,right] + print('区间缩小为[%d,%d]' %(mid+1,right)) + + # 同理,假如x更小,则不可能出现在右半部分 + elif x + +```python +import requests +from lxml import etree +import pandas as pd +import re + +url = 'http://www.weather.com.cn/weather1d/101010100.shtml#input' +with requests.get(url) as res: + content = res.content + html = etree.HTML(content) +``` + + + +通过lxml模块提取值 + +lxml比beautifulsoup解析在某些场合更高效 + +```python +location = html.xpath('//*[@id="around"]//a[@target="_blank"]/span/text()') +temperature = html.xpath('//*[@id="around"]/div/ul/li/a/i/text()') +``` + +结果: + +```python +['香河', '涿州', '唐山', '沧州', '天津', '廊坊', '太原', '石家庄', '涿鹿', '张家口', '保定', '三河', '北京孔庙', '北京国子监', '中国地质博物馆', '月坛公 +园', '明城墙遗址公园', '北京市规划展览馆', '什刹海', '南锣鼓巷', '天坛公园', '北海公园', '景山公园', '北京海洋馆'] + +['11/-5°C', '14/-5°C', '12/-6°C', '12/-5°C', '11/-1°C', '11/-5°C', '8/-7°C', '13/-2°C', '8/-6°C', '5/-9°C', '14/-6°C', '11/-4°C', '13/-3°C' +, '13/-3°C', '12/-3°C', '12/-3°C', '13/-3°C', '12/-2°C', '12/-3°C', '13/-3°C', '12/-2°C', '12/-2°C', '12/-2°C', '12/-3°C'] +``` + + +构造DataFrame对象 + +```python +df = pd.DataFrame({'location':location, 'temperature':temperature}) +print('温度列') +print(df['temperature']) +``` + +正则解析温度值 + +```python +df['high'] = df['temperature'].apply(lambda x: int(re.match('(-?[0-9]*?)/-?[0-9]*?°C', x).group(1) ) ) +df['low'] = df['temperature'].apply(lambda x: int(re.match('-?[0-9]*?/(-?[0-9]*?)°C', x).group(1) ) ) +print(df) +``` + +详细说明子字符创捕获 + +除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用`()`表示的就是要提取的分组(group)。比如:`^(\d{3})-(\d{3,8})$`分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码 + +```python +m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345') +print(m.group(0)) +print(m.group(1)) +print(m.group(2)) + +# 010-12345 +# 010 +# 12345 +``` + +如果正则表达式中定义了组,就可以在`Match`对象上用`group()`方法提取出子串来。 + +注意到`group(0)`永远是原始字符串,`group(1)`、`group(2)`……表示第1、2、……个子串。 + + +最终结果 + +```kepython +Name: temperature, dtype: object + location temperature high low +0 香河 11/-5°C 11 -5 +1 涿州 14/-5°C 14 -5 +2 唐山 12/-6°C 12 -6 +3 沧州 12/-5°C 12 -5 +4 天津 11/-1°C 11 -1 +5 廊坊 11/-5°C 11 -5 +6 太原 8/-7°C 8 -7 +7 石家庄 13/-2°C 13 -2 +8 涿鹿 8/-6°C 8 -6 +9 张家口 5/-9°C 5 -9 +10 保定 14/-6°C 14 -6 +11 三河 11/-4°C 11 -4 +12 北京孔庙 13/-3°C 13 -3 +13 北京国子监 13/-3°C 13 -3 +14 中国地质博物馆 12/-3°C 12 -3 +15 月坛公园 12/-3°C 12 -3 +16 明城墙遗址公园 13/-3°C 13 -3 +17 北京市规划展览馆 12/-2°C 12 -2 +18 什刹海 12/-3°C 12 -3 +19 南锣鼓巷 13/-3°C 13 -3 +20 天坛公园 12/-2°C 12 -2 +21 北海公园 12/-2°C 12 -2 +22 景山公园 12/-2°C 12 -2 +23 北京海洋馆 12/-3°C 12 -3 +``` + +### 十、数据分析 + +本项目基于Kaggle电影影评数据集,通过这个系列,你将学到如何进行数据探索性分析(EDA),学会使用数据分析利器`pandas`,会用绘图包`pyecharts`,以及EDA时可能遇到的各种实际问题及一些处理技巧。 + + + +本项目需要导入的包: + +```python +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +from pyecharts.charts import Bar,Grid,Line +import pyecharts.options as opts +from pyecharts.globals import ThemeType +``` + +#### 1 创建DataFrame +pandas中一个dataFrame实例: +```python +Out[89]: + a val +0 apple1 1.0 +1 apple2 2.0 +2 apple3 3.0 +3 apple4 4.0 +4 apple5 5.0 +``` + +我们的**目标**是变为如下结构: +```python +a apple1 apple2 apple3 apple4 apple5 +0 1.0 2.0 3.0 4.0 5.0 +``` + +乍看可使用`pivot`,但很难一步到位。 + +所以另辟蹊径,提供一种简单且好理解的方法: + +```python +In [113]: pd.DataFrame(index=[0],columns=df.a,data=dict(zip(df.a,df.val))) +Out[113]: +a apple1 apple2 apple3 apple4 apple5 +0 1.0 2.0 3.0 4.0 5.0 +``` +以上方法是重新创建一个DataFrame,直接把`df.a`所有可能取值作为新dataframe的列,index调整为`[0]`,注意类型必须是数组类型(array-like 或者 Index),两个轴确定后,`data`填充数据域。 + +```python +In [116]: dict(zip(df.a,df.val)) +Out[116]: {'apple1': 1.0, 'apple2': 2.0, 'apple3': 3.0, 'apple4': 4.0, 'apple5': 5.0} +``` + + + +#### 2 导入数据 +数据来自kaggle,共包括三个文件: + +1. movies.dat +2. ratings.dat +3. users.dat + +`movies.dat`包括三个字段:['Movie ID', 'Movie Title', 'Genre'] + +使用pandas导入此文件: + +```python +import pandas as pd + +movies = pd.read_csv('./data/movietweetings/movies.dat', delimiter='::', engine='python', header=None, names = ['Movie ID', 'Movie Title', 'Genre']) +``` + +导入后,显示前5行: + +```python + Movie ID Movie Title \ +0 8 Edison Kinetoscopic Record of a Sneeze (1894) +1 10 La sortie des usines Lumi猫re (1895) +2 12 The Arrival of a Train (1896) +3 25 The Oxford and Cambridge University Boat Race ... +4 91 Le manoir du diable (1896) +5 131 Une nuit terrible (1896) +6 417 Le voyage dans la lune (1902) +7 439 The Great Train Robbery (1903) +8 443 Hiawatha, the Messiah of the Ojibway (1903) +9 628 The Adventures of Dollie (1908) + Genre +0 Documentary|Short +1 Documentary|Short +2 Documentary|Short +3 NaN +4 Short|Horror +5 Short|Comedy|Horror +6 Short|Action|Adventure|Comedy|Fantasy|Sci-Fi +7 Short|Action|Crime|Western +8 NaN +9 Action|Short +``` + + + +次导入其他两个数据文件 + +`users.dat`: + +```python +users = pd.read_csv('./data/movietweetings/users.dat', delimiter='::', engine='python', header=None, names = ['User ID', 'Twitter ID']) +print(users.head()) +``` + +结果: + +```python + User ID Twitter ID +0 1 397291295 +1 2 40501255 +2 3 417333257 +3 4 138805259 +4 5 2452094989 +5 6 391774225 +6 7 47317010 +7 8 84541461 +8 9 2445803544 +9 10 995885060 +``` + + + +`rating.data`: + +```python +ratings = pd.read_csv('./data/movietweetings/ratings.dat', delimiter='::', engine='python', header=None, names = ['User ID', 'Movie ID', 'Rating', 'Rating Timestamp']) +print(ratings.head()) +``` + +结果: + +```python + User ID Movie ID Rating Rating Timestamp +0 1 111161 10 1373234211 +1 1 117060 7 1373415231 +2 1 120755 6 1373424360 +3 1 317919 6 1373495763 +4 1 454876 10 1373621125 +5 1 790724 8 1374641320 +6 1 882977 8 1372898763 +7 1 1229238 9 1373506523 +8 1 1288558 5 1373154354 +9 1 1300854 8 1377165712 +``` + + **read_csv 使用说明** + +说明,本次导入`dat`文件使用`pandas.read_csv`函数。 + +第一个位置参数`./data/movietweetings/ratings.dat` 表示文件的相对路径 + +第二个关键字参数:`delimiter='::'`,表示文件分隔符使用`::` + +后面几个关键字参数分别代表使用的引擎,文件没有表头,所以`header`为`None;` + +导入后dataframe的列名使用`names`关键字设置,这个参数大家可以记住,比较有用。 + + + +Kaggle电影数据集第一节,我们使用数据处理利器 `pandas`, 函数`read_csv` 导入给定的三个数据文件。 + +```python +import pandas as pd + +movies = pd.read_csv('./data/movietweetings/movies.dat', delimiter='::', engine='python', header=None, names = ['Movie ID', 'Movie Title', 'Genre']) +users = pd.read_csv('./data/movietweetings/users.dat', delimiter='::', engine='python', header=None, names = ['User ID', 'Twitter ID']) +ratings = pd.read_csv('./data/movietweetings/ratings.dat', delimiter='::', engine='python', header=None, names = ['User ID', 'Movie ID', 'Rating', 'Rating Timestamp']) +``` + +用到的`read_csv`,某些重要的参数,如何使用在上一节也有所提到。下面开始数据探索分析(EDA) + +> 找出得分前10喜剧(comedy) + + + +#### 3 处理组合值 + +表`movies`字段`Genre`表示电影的类型,可能有多个值,分隔符为`|`,取值也可能为`None`. + +针对这类字段取值,可使用Pandas中Series提供的`str`做一步转化,**注意它是向量级的**,下一步,如Python原生的`str`类似,使用`contains`判断是否含有`comedy`字符串: + +```python +mask = movies.Genre.str.contains('comedy',case=False,na=False) +``` + +注意使用的两个参数:`case`, `na` + +case为 False,表示对大小写不敏感; +na Genre列某个单元格为`NaN`时,我们使用的充填值,此处填充为`False` + +返回的`mask`是一维的`Series`,结构与 movies.Genre相同,取值为True 或 False. + +观察结果: + +```python +0 False +1 False +2 False +3 False +4 False +5 True +6 True +7 False +8 False +9 False +Name: Genre, dtype: bool + +``` + + + #### 4 访问某列 + +得到掩码mask后,pandas非常方便地能提取出目标记录: + +```python +comedy = movies[mask] +comdey_ids = comedy['Movie ID'] + +``` + +以上,在pandas中被最频率使用,不再解释。看结果`comedy_ids.head()`: + +```python +5 131 +6 417 +15 2354 +18 3863 +19 4099 +20 4100 +21 4101 +22 4210 +23 4395 +25 4518 +Name: Movie ID, dtype: int64 + +``` + + + +1-4介绍`数据读入`,`处理组合值`,`索引数据`等, pandas中使用较多的函数,基于Kaggle真实电影影评数据集,最后得到所有`喜剧 ID`: + +```python +5 131 +6 417 +15 2354 +18 3863 +19 4099 +20 4100 +21 4101 +22 4210 +23 4395 +25 4518 +Name: Movie ID, dtype: int64 + +``` + +下面继续数据探索之旅~ + +#### 5 连接两个表 + +拿到所有喜剧的ID后,要想找出其中平均得分最高的前10喜剧,需要关联另一张表:`ratings`: + +再回顾下ratings表结构: + +```python + User ID Movie ID Rating Rating Timestamp +0 1 111161 10 1373234211 +1 1 117060 7 1373415231 +2 1 120755 6 1373424360 +3 1 317919 6 1373495763 +4 1 454876 10 1373621125 +5 1 790724 8 1374641320 +6 1 882977 8 1372898763 +7 1 1229238 9 1373506523 +8 1 1288558 5 1373154354 +9 1 1300854 8 1377165712 + +``` + + +pandas 中使用`join`关联两张表,连接字段是`Movie ID`,如果顺其自然这么使用`join`: + +```python +combine = ratings.join(comedy, on='Movie ID', rsuffix='2') + +``` + +左右滑动,查看完整代码 + +大家可验证这种写法,仔细一看,会发现结果非常诡异。 + +究其原因,这是pandas join函数使用的一个算是坑点,它在官档中介绍,连接右表时,此处右表是`comedy`,它的`index`要求是连接字段,也就是 `Movie ID`. + +左表的index不要求,但是要在参数 `on`中给定。 + +**以上是要注意的一点** + +修改为: + +```python +combine = ratings.join(comedy.set_index('Movie ID'), on='Movie ID') +print(combine.head(10)) + +``` + +以上是OK的写法 + +观察结果: + +```python + User ID Movie ID Rating Rating Timestamp Movie Title Genre +0 1 111161 10 1373234211 NaN NaN +1 1 117060 7 1373415231 NaN NaN +2 1 120755 6 1373424360 NaN NaN +3 1 317919 6 1373495763 NaN NaN +4 1 454876 10 1373621125 NaN NaN +5 1 790724 8 1374641320 NaN NaN +6 1 882977 8 1372898763 NaN NaN +7 1 1229238 9 1373506523 NaN NaN +8 1 1288558 5 1373154354 NaN NaN +9 1 1300854 8 1377165712 NaN NaN + +``` + +Genre列为`NaN`表明,这不是喜剧。需要筛选出此列不为`NaN` 的记录。 + +#### 6 按列筛选 + +pandas最方便的地方,就是向量化运算,尽可能减少了for循环的嵌套。 + +按列筛选这种常见需求,自然可以轻松应对。 + +为了照顾初次接触 pandas 的朋友,分两步去写: + +```python +mask = pd.notnull(combine['Genre']) + +``` + +结果是一列只含`True 或 False`的值 + +```python +result = combine[mask] +print(result.head()) + +``` + +结果中,Genre字段中至少含有一个Comedy字符串,表明验证了我们以上操作是OK的。 + +```python + User ID Movie ID Rating Rating Timestamp Movie Title \ +12 1 1588173 9 1372821281 Warm Bodies (2013) +13 1 1711425 3 1372604878 21 & Over (2013) +14 1 2024432 8 1372703553 Identity Thief (2013) +17 1 2101441 1 1372633473 Spring Breakers (2012) +28 2 1431045 7 1457733508 Deadpool (2016) + + Genre +12 Comedy|Horror|Romance +13 Comedy +14 Adventure|Comedy|Crime|Drama +17 Comedy|Crime|Drama +28 Action|Adventure|Comedy|Sci-Fi + + +``` + + + +截止目前已经求出所有喜剧电影`result`,前5行如下,Genre中都含有`Comedy`字符串: +```python + User ID Movie ID Rating Rating Timestamp Movie Title \ +12 1 1588173 9 1372821281 Warm Bodies (2013) +13 1 1711425 3 1372604878 21 & Over (2013) +14 1 2024432 8 1372703553 Identity Thief (2013) +17 1 2101441 1 1372633473 Spring Breakers (2012) +28 2 1431045 7 1457733508 Deadpool (2016) + + Genre +12 Comedy|Horror|Romance +13 Comedy +14 Adventure|Comedy|Crime|Drama +17 Comedy|Crime|Drama +28 Action|Adventure|Comedy|Sci-Fi +``` + + + +#### 7 按照Movie ID 分组 + +result中会有很多观众对同一部电影的打分,所以要求得分前10的喜剧,先按照`Movie ID`分组,然后求出平均值: +```python +score_as_movie = result.groupby('Movie ID').mean() +``` + +前5行显示如下: +```python + User ID Rating Rating Timestamp +Movie ID +131 34861.000000 7.0 1.540639e+09 +417 34121.409091 8.5 1.458680e+09 +2354 6264.000000 8.0 1.456343e+09 +3863 43803.000000 10.0 1.430439e+09 +4099 25084.500000 7.0 1.450323e+09 +``` + +#### 8 按照电影得分排序 + +```python +score_as_movie.sort_values(by='Rating', ascending = False,inplace=True) +score_as_movie +``` +前5行显示如下: +```python + User ID Rating Rating Timestamp +Movie ID +7134690 30110.0 10.0 1.524974e+09 +416889 1319.0 10.0 1.543320e+09 +57840 23589.0 10.0 1.396802e+09 +5693562 50266.0 10.0 1.511024e+09 +5074 43803.0 10.0 1.428352e+09 +``` +都是满分?这有点奇怪,会不会这些电影都只有几个人评分,甚至只有1个?评分样本个数太少,显然最终的平均分数不具有太强的说服力。 + +所以,下面要进行每部电影的评分人数统计 + +#### 9 分组后使用聚合函数 + +根据`Movie ID`分组后,使用`count`函数统计`每组个数`,只保留count列,最后得到`watchs2`: + +```python +watchs = result.groupby('Movie ID').agg(['count']) +watchs2 = watchs['Rating']['count'] +``` +打印前20行: +```python +print(watchs2.head(20)) +``` +结果: +```python +Movie ID +131 1 +417 22 +2354 1 +3863 1 +4099 2 +4100 1 +4101 1 +4210 1 +4395 1 +4518 1 +4546 2 +4936 2 +5074 1 +5571 1 +6177 1 +6414 3 +6684 1 +6689 1 +7145 1 +7162 2 +Name: count, dtype: int64 +``` +果然,竟然有这么多电影的评论数只有1次!样本个数太少,评论的平均值也就没有什么说服力。 + +查看`watchs2`一些重要统计量: +```python +watchs2.describe() +``` +结果: +```python +count 10740.000000 +mean 20.192086 +std 86.251411 +min 1.000000 +25% 1.000000 +50% 2.000000 +75% 7.000000 +max 1843.000000 +Name: count, dtype: float64 +``` +共有10740部**喜剧**电影被评分,平均打分次数20次,标准差86,75%的电影样本打分次数小于7次,最小1次,最多1843次。 + +#### 10 频率分布直方图 + +绘制评论数的频率分布直方图,便于更直观的观察电影被评论的分布情况。上面分析到,75%的电影打分次数小于7次,所以绘制打分次数小于20次的直方图: + +```python +fig = plt.figure(figsize=(12,8)) +histn = plt.hist(watchs2[watchs2 <=19],19,histtype='step') +plt.scatter([i+1 for i in range(len(histn[0]))],histn[0]) +``` + +![](./img/20200131094927.jpg) + +`histn`元祖表示个数和对应的被分割的区间,查看`histn[0]`: +```python +array([4383., 1507., 787., 541., 356., 279., 209., 163., 158., + 118., 114., 90., 104., 81., 80., 73., 62., 65., + 52.]) +``` +```python +sum(histn[0]) # 9222 +``` +看到电影评论次数1到19次的喜剧电影9222部,共有10740部喜剧电影,大约`86%`的喜剧电影评论次数`小于20次`,有`1518`部电影评论数不小于20次。 + +我们肯定希望挑选出被评论次数尽可能多的电影,因为难免会有水军和滥竽充数等`异常评论`行为。那么,如何准确的量化最小抽样量呢? + + + +#### 11 最小抽样量 + +根据统计学的知识,最小抽样量和Z值、样本方差和样本误差相关,下面给出具体的求解最小样本量的计算方法。 + +采用如下计算公式: + +$$ n = \frac{Z^2\sigma^2}{E^2} $$ + + +此处,$Z$ 值取为95%的置信度对应的Z值也就是1.96,样本误差取为均值的2.5%. + +根据以上公式,编写下面代码: + +```python +n3 = result.groupby('Movie ID').agg(['count','mean','std']) +n3r = n3[n3['Rating']['count']>=20]['Rating'] +``` +只计算影评超过20次,且满足最小样本量的电影。计算得到的`n3r`前5行: +```python + count mean std +Movie ID +417 22 8.500000 1.263027 +12349 68 8.485294 1.227698 +15324 20 8.350000 1.039990 +15864 51 8.431373 1.374844 +17925 44 8.636364 1.259216 +``` +进一步求出最小样本量: +```python +nmin = (1.96**2*n3r['std']**2) / ( (n3r['mean']*0.025)**2 ) +``` +`nmin`前5行: +```python +Movie ID +417 135.712480 +12349 128.671290 +15324 95.349276 +15864 163.434005 +17925 130.668350 +``` + +筛选出满足最小抽样量的喜剧电影: + +```python +n3s = n3r[ n3r['count'] >= nmin ] +``` +结果显示如下,因此共有`173`部电影满足最小样本抽样量。 + +```python + +count mean std +Movie ID +53604 129 8.635659 1.230714 +57012 207 8.449275 1.537899 +70735 224 8.839286 1.190799 +75686 209 8.095694 1.358885 +88763 296 8.945946 1.026984 +... ... ... ... +6320628 860 7.966279 1.469924 +6412452 276 7.510870 1.389529 +6662050 22 10.000000 0.000000 +6966692 907 8.673649 1.286455 +7131622 1102 7.851180 1.751500 +173 rows × 3 columns +``` + +#### 12 去重和连表 + +按照平均得分从大到小排序: +```python +n3s_sort = n3s.sort_values(by='mean',ascending=False) +``` +结果: +```python + count mean std +Movie ID +6662050 22 10.000000 0.000000 +4921860 48 10.000000 0.000000 +5262972 28 10.000000 0.000000 +5512872 353 9.985836 0.266123 +3863552 199 9.010050 1.163372 +... ... ... ... +1291150 647 6.327666 1.785968 +2557490 546 6.307692 1.858434 +1478839 120 6.200000 0.728761 +2177771 485 6.150515 1.523922 +1951261 1091 6.083410 1.736127 +173 rows × 3 columns +``` +仅靠`Movie ID`还是不知道哪些电影,连接`movies`表: +```python +ms = movies.drop_duplicates(subset=['Movie ID']) +ms = ms.set_index('Movie ID') +n3s_final = n3s_drops.join(ms,on='Movie ID') +``` + +#### 13 结果分析 + +喜剧榜单前50名: +```python +Movie Title +Five Minutes (2017) +MSG 2 the Messenger (2015) +Avengers: Age of Ultron Parody (2015) +Be Somebody (2016) +Bajrangi Bhaijaan (2015) +Back to the Future (1985) +La vita 鐚?bella (1997) +The Intouchables (2011) +The Sting (1973) +Coco (2017) +Toy Story 3 (2010) +3 Idiots (2009) +Green Book (2018) +Dead Poets Society (1989) +The Apartment (1960) +P.K. (2014) +The Truman Show (1998) +Am鑼卨ie (2001) +Inside Out (2015) +Toy Story 4 (2019) +Toy Story (1995) +Finding Nemo (2003) +Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb (1964) +Home Alone (1990) +Zootopia (2016) +Up (2009) +Monsters, Inc. (2001) +La La Land (2016) +Relatos salvajes (2014) +En man som heter Ove (2015) +Snatch (2000) +Lock, Stock and Two Smoking Barrels (1998) +How to Train Your Dragon 2 (2014) +As Good as It Gets (1997) +Guardians of the Galaxy (2014) +The Grand Budapest Hotel (2014) +Fantastic Mr. Fox (2009) +Silver Linings Playbook (2012) +Sing Street (2016) +Deadpool (2016) +Annie Hall (1977) +Pride (2014) +In Bruges (2008) +Big Hero 6 (2014) +Groundhog Day (1993) +The Breakfast Club (1985) +Little Miss Sunshine (2006) +Deadpool 2 (2018) +The Terminal (2004) +``` + +前10名评论数图: + +![](./img/2020013109495711.jpg) + +代码: +```python +x = n3s_final['Movie Title'][:10].tolist()[::-1] +y = n3s_final['count'][:10].tolist()[::-1] +bar = ( + Bar() + .add_xaxis(x) + .add_yaxis('评论数',y,category_gap='50%') + .reversal_axis() + .set_global_opts(title_opts=opts.TitleOpts(title="喜剧电影被评论次数"), + toolbox_opts=opts.ToolboxOpts(),) +) +grid = ( + Grid(init_opts=opts.InitOpts(theme=ThemeType.LIGHT)) + .add(bar, grid_opts=opts.GridOpts(pos_left="30%")) +) +grid.render_notebook() +``` + +前10名得分图: + +![](./img/2020013109500812.jpg) + +代码: +```python +x = n3s_final['Movie Title'][:10].tolist()[::-1] +y = n3s_final['mean'][:10].round(3).tolist()[::-1] +bar = ( + Bar() + .add_xaxis(x) + .add_yaxis('平均得分',y,category_gap='50%') + .reversal_axis() + .set_global_opts(title_opts=opts.TitleOpts(title="喜剧电影平均得分"), + xaxis_opts=opts.AxisOpts(min_=8.0,name='平均得分'), + toolbox_opts=opts.ToolboxOpts(),) +) +grid = ( + Grid(init_opts=opts.InitOpts(theme=ThemeType.MACARONS)) + .add(bar, grid_opts=opts.GridOpts(pos_left="30%")) +) +grid.render_notebook() +``` + + + +#### 14 生成哑变量 + +分类变量的数值化,是指将枚举类变量转化为indicator变量或称dummy变量。 + +那么什么是`indicator变量`,看看如下例子,A变量解析为:`[1,0,0]`, B解析为:`[0,1,0]`, C解析为:`[0,0,1]` +```python +In [8]: s = pd.Series(list('ABCA')) +In [9]: pd.get_dummies(s) +Out[9]: + A B C +0 1 0 0 +1 0 1 0 +2 0 0 1 +3 1 0 0 +``` + +如果输入的字符有4个唯一值,看到字符a被解析为[1,0,0,0],向量长度为4. + +```python +In [5]: s = pd.Series(list('abaccd')) +In [6]: pd.get_dummies(s) +Out[6]: + a b c d +0 1 0 0 0 +1 0 1 0 0 +2 1 0 0 0 +3 0 0 1 0 +4 0 0 1 0 +5 0 0 0 1 +``` + +也就是说dummy向量的长度等于输入字符串中,唯一字符的个数。 + +#### 15 讨厌的SettingWithCopyWarning!!! + +Pandas 处理数据,太好用了,谁用谁知道! + +使用过 Pandas 的,几乎都会遇到一个警告: + +*SettingWithCopyWarning* + +非常烦人! + +尤其是刚接触 Pandas 的,完全不理解为什么弹出这么一串: + +```python +d:\source\test\settingwithcopy.py:9: SettingWithCopyWarning: +A value is trying to be set on a copy of a slice from a DataFrame. +Try using .loc[row_indexer,col_indexer] = value instead + +See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy +``` + +归根结底,是因为代码中出现`链式操作`... + +有人就问了,什么是`链式操作`? + +这样的: + +```python +tmp = df[df.a<4] +tmp['c'] = 200 +``` + +先记住这个最典型的情况,即可! + +有的人就问了:出现这个 Warning, 需要理会它吗? + +如果结果不对,当然要理会;如果结果对,不care. + +举个例子~~ + +```python +import pandas as pd + +df = pd.DataFrame({'a':[1,3,5],'b':[4,2,7]},index=['a','b','c']) +df.loc[df.a<4,'c'] = 100 +print(df) +print('it\'s ok') + +tmp = df[df.a<4] +tmp['c'] = 200 +print('-----tmp------') +print(tmp) +print('-----df-------') +print(df) +``` + +输出结果: +```python + a b c +a 1 4 100.0 +b 3 2 100.0 +c 5 7 NaN +it's ok +d:\source\test\settingwithcopy.py:9: SettingWithCopyWarning: +A value is trying to be set on a copy of a slice from a DataFrame. +Try using .loc[row_indexer,col_indexer] = value instead + +See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy + tmp['c'] = 200 +-----tmp------ + a b c +a 1 4 200 +b 3 2 200 +-----df------- + a b c +a 1 4 100.0 +b 3 2 100.0 +c 5 7 NaN +``` + +it's ok 行后面的发生链式赋值,导致结果错误。因为 tmp 变了,df 没赋上值啊,所以必须理会。 + +it's ok 行前的是正解。 + +以上,链式操作尽量避免,如何避免?多使用 `.loc[row_indexer,col_indexer]`,提示告诉我们的~ + +#### 16 NumPy 数据归一化、分布可视化 + +仅使用 `NumPy`,下载数据,归一化,使用 `seaborn` 展示数据分布。 + +**下载数据** + +```python +import numpy as np + +url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data' +wid = np.genfromtxt(url, delimiter=',', dtype='float', usecols=[1]) +``` +仅提取 `iris` 数据集的第二列 `usecols = [1]` + +**展示数据** + +```python +array([3.5, 3. , 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3.4, 3. , + 3. , 4. , 4.4, 3.9, 3.5, 3.8, 3.8, 3.4, 3.7, 3.6, 3.3, 3.4, 3. , + 3.4, 3.5, 3.4, 3.2, 3.1, 3.4, 4.1, 4.2, 3.1, 3.2, 3.5, 3.1, 3. , + 3.4, 3.5, 2.3, 3.2, 3.5, 3.8, 3. , 3.8, 3.2, 3.7, 3.3, 3.2, 3.2, + 3.1, 2.3, 2.8, 2.8, 3.3, 2.4, 2.9, 2.7, 2. , 3. , 2.2, 2.9, 2.9, + 3.1, 3. , 2.7, 2.2, 2.5, 3.2, 2.8, 2.5, 2.8, 2.9, 3. , 2.8, 3. , + 2.9, 2.6, 2.4, 2.4, 2.7, 2.7, 3. , 3.4, 3.1, 2.3, 3. , 2.5, 2.6, + 3. , 2.6, 2.3, 2.7, 3. , 2.9, 2.9, 2.5, 2.8, 3.3, 2.7, 3. , 2.9, + 3. , 3. , 2.5, 2.9, 2.5, 3.6, 3.2, 2.7, 3. , 2.5, 2.8, 3.2, 3. , + 3.8, 2.6, 2.2, 3.2, 2.8, 2.8, 2.7, 3.3, 3.2, 2.8, 3. , 2.8, 3. , + 2.8, 3.8, 2.8, 2.8, 2.6, 3. , 3.4, 3.1, 3. , 3.1, 3.1, 3.1, 2.7, + 3.2, 3.3, 3. , 2.5, 3. , 3.4, 3. ]) + +``` + +这是单变量(univariate)长度为 150 的一维 NumPy 数组。 + +**归一化** + +求出最大值、最小值 +```python +smax = np.max(wid) +smin = np.min(wid) + +In [51]: smax,smin +Out[51]: (4.4, 2.0) +```` +归一化公式: +```python +s = (wid - smin) / (smax - smin) +``` +只打印小数点后三位设置: +```python +np.set_printoptions(precision=3) +``` + +归一化结果: +```markdown +array([0.625, 0.417, 0.5 , 0.458, 0.667, 0.792, 0.583, 0.583, 0.375, + 0.458, 0.708, 0.583, 0.417, 0.417, 0.833, 1. , 0.792, 0.625, + 0.75 , 0.75 , 0.583, 0.708, 0.667, 0.542, 0.583, 0.417, 0.583, + 0.625, 0.583, 0.5 , 0.458, 0.583, 0.875, 0.917, 0.458, 0.5 , + 0.625, 0.458, 0.417, 0.583, 0.625, 0.125, 0.5 , 0.625, 0.75 , + 0.417, 0.75 , 0.5 , 0.708, 0.542, 0.5 , 0.5 , 0.458, 0.125, + 0.333, 0.333, 0.542, 0.167, 0.375, 0.292, 0. , 0.417, 0.083, + 0.375, 0.375, 0.458, 0.417, 0.292, 0.083, 0.208, 0.5 , 0.333, + 0.208, 0.333, 0.375, 0.417, 0.333, 0.417, 0.375, 0.25 , 0.167, + 0.167, 0.292, 0.292, 0.417, 0.583, 0.458, 0.125, 0.417, 0.208, + 0.25 , 0.417, 0.25 , 0.125, 0.292, 0.417, 0.375, 0.375, 0.208, + 0.333, 0.542, 0.292, 0.417, 0.375, 0.417, 0.417, 0.208, 0.375, + 0.208, 0.667, 0.5 , 0.292, 0.417, 0.208, 0.333, 0.5 , 0.417, + 0.75 , 0.25 , 0.083, 0.5 , 0.333, 0.333, 0.292, 0.542, 0.5 , + 0.333, 0.417, 0.333, 0.417, 0.333, 0.75 , 0.333, 0.333, 0.25 , + 0.417, 0.583, 0.458, 0.417, 0.458, 0.458, 0.458, 0.292, 0.5 , + 0.542, 0.417, 0.208, 0.417, 0.583, 0.417]) +``` + +**分布可视化** + +```python +import seaborn as sns +sns.distplot(s,kde=False,rug=True) +``` +频率分布直方图: + + +![](https://imgkr.cn-bj.ufileos.com/49bf5190-429c-4172-a53c-e3f6b66d4e64.png) + + +```python +sns.distplot(s,hist=True,kde=True,rug=True) +``` +带高斯密度核函数的直方图: + +![](https://imgkr.cn-bj.ufileos.com/4e4a72a5-8f59-4893-b435-e4b57e22a18e.png) + + + +**分布 fit 图** + +拿 `gamma` 分布去 fit : +```python +from scipy import stats +sns.distplot(s, kde=False, fit = stats.gamma) +``` + + + +![](https://imgkr.cn-bj.ufileos.com/89446755-7420-4f96-97fe-c4e45d0d3dec.png) + + +拿双 `gamma` 去 fit: +```python +from scipy import stats +sns.distplot(s, kde=False, fit = stats.dgamma) +``` + +![](https://imgkr.cn-bj.ufileos.com/f2c2a660-5433-4b4f-ad7b-d01da4121319.png) + +#### 17 Pandas 使用技巧 + +对于动辄就几十或几百个 G 的数据,在读取的这么大数据的时候,我们有没有办法随机选取一小部分数据,然后读入内存,快速了解数据和开展 EDA ? + +使用 Pandas 的 skiprows 和 概率知识,就能做到。 + +下面解释具体怎么做。 + +如下所示,读取某 100 G 大小的 big_data.csv 数据 + +1) 使用 skiprows 参数, + +2) x > 0 确保首行读入, + +3) np.random.rand() > 0.01 表示 99% 的数据都会被随机过滤掉 + +言外之意,只有全部数据的 1% 才有机会选入内存中。 + +```python +import pandas as pd +import numpy as np + +df = pd.read_csv("big_data.csv", +skiprows = +lambda x: x>0 and np.random.rand() > 0.01) + +print("The shape of the df is {}. +It has been reduced 100 times!".format(df.shape)) +``` + +使用这种方法,读取的数据量迅速缩减到原来的 1% ,对于迅速展开数据分析有一定的帮助。 + +### 十一、一步一步掌握Flask web开发 + +#### 1 Flask版 hello world + +Flask是Python轻量级web框架,容易上手,被广大Python开发者所喜爱。 + +今天我们先从hello world开始,一步一步掌握Flask web开发。例子君是Flask框架的小白,接下来与读者朋友们,一起学习这个对我而言的新框架,大家多多指导。 + +首先`pip install Flask`,安装Flask,然后import Flask,同时创建一个 `app` +```python +from flask import Flask + +App = Flask(__name__) +``` + +写一个index页的入口函数,返回hello world. + +通过装饰器:App.route('/')创建index页的路由或地址,一个`/`表示index页,也就是主页。 + +```python +@App.route('/') +def index(): + return "hello world" +``` + +调用 `index`函数: +```python +if __name__ == "__main__": + App.run(debug=True) +``` + +然后启动,会在console下看到如下启动信息,表明`服务启动成功`。 +```python +* Debug mode: on + * Restarting with stat + * Debugger is active! + * Debugger PIN: 663-788-611 + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) +``` + + 接下来,打开一个网页,相当于启动客户端,并在Url栏中输入:`http://127.0.0.1:5000/`,看到页面上答应出`hello world`,证明服务访问成功。 + + 同时在服务端后台看到如下信息,表示处理一次来自客户端的`get`请求。 + ```python + 27.0.0.1 - - [03/Feb/2020 21:26:50] "GET / HTTP/1.1" 200 - + ``` + + 以上就是flask的hello world 版 + +#### 2 Flask之数据入库操作 + +数据持久化就是将数据写入到数据库存储的过程。 + +本例子使用`sqlite3`数据库。 + +1)导入`sqlite3`,未安装前使用命令`pip install sqlite3` + +创建一个`py`文件:`sqlite3_started.py`,并写下第一行代码: +```python +import sqlite3 +``` +2)手动创建一个数据库实例`db`, 命名`test.db` + +3)创建与数据库实例`test.db`的连接: +```python +conn = sqlite3.connect("test.db") +``` + +4)拿到连接`conn`的cursor +```python +c = conn.cursor() +``` + +5)创建第一张表`books` + +共有四个字段:`id`,`sort`,`name`,`price`,类型分别为:`int`,`int`,`text`,`real`. 其中`id`为`primary key`. 主键的取值必须是唯一的(`unique`),否则会报错。 + + +```python +c.execute('''CREATE TABLE books + (id int primary key, + sort int, + name text, + price real)''') +``` +第一次执行上面语句,表`books`创建完成。当再次执行时,就会报`重复建表`的错误。需要优化脚本,检查表是否存在`IF NOT EXISTS books`,不存在再创建: +```python +c.execute('''CREATE TABLE IF NOT EXISTS books + (id int primary key, + sort int, + name text, + price real)''') +``` + +6)插入一行记录 + +共为4个字段赋值 + +```python +c.execute('''INSERT INTO books VALUES + (1, + 1, + 'computer science', + 39.0)''') +``` + +7)一次插入多行记录 + +先创建一个list:`books`,使用`executemany`一次插入多行。 +```python +books = [(2, 2, 'Cook book', 68), + (3, 2, 'Python intro', 89), + (4, 3, 'machine learning', 59), + ] + + +c.executemany('INSERT INTO books VALUES (?, ?, ?, ?)', books) +``` + +8)提交 + +提交后才会真正生效,写入到数据库 + +```python +conn.commit() +``` + +9)关闭期初建立的连接conn + +务必记住手动关闭,否则会出现内存泄漏 +```python +conn.close() +print('Done') +``` + +10)查看结果 +例子君使用`vs code`,在扩展库中选择:`SQLite`安装。 + +![image-20200208211721377](./img/image-20200208211721377.png) + +新建一个`sq`文件:`a.sql`,内容如下: + +```sql +SELECT * from books +``` +右键`run query`,得到表`books`插入的4行记录可视化图: + +![image-20200208211806853](./img/image-20200208211806853.png) + +以上十步就是sqlite3写入数据库的主要步骤,作为Flask系列的第二篇,为后面的前端讲解打下基础。 + +#### 3 Flask各层调用关系 + +这篇介绍Flask和B/S模式,即浏览器/服务器模式,是接下来快速理解Flask代码的关键理论篇:**理解Views、models和渲染模板层的调用关系**。 + +1) 发出请求 + +当我们在浏览器地址栏中输入某个地址,按回车后,完成第一步。 + +2) 视图层 views接收1)步发出的请求,Flask中使用解释器的方式处理这个求情,实例代码如下,它通常涉及到调用models层和模板文件层 + +```python +@main_blue.route('/', methods=['GET', 'POST']) +def index(): + form = TestForm() + print('test') +``` + +3) models层会负责创建数据模型,执行CRUD操作 + +4) 模板文件层处理html模板 + +5) 组合后返回html + +6) models层和html模板组合后返回给views层 + +7)最后views层响应并渲染到浏览器页面,我们就能看到请求的页面。 + +完整过程图如下所示: + +![image-20200211152007983](./img/web1.png) + +读者朋友们,如果你和例子君一样都是初学Flask编程,需要好好理解上面的过程。理解这些对于接下来的编程会有一定的理论指导,方向性指导价值。 + +### Python 问答 + +#### Python 如何生成二维码? + + + + + +## qrcode + +今天先来解答如何生成二维码。Python的`qrcode`包支持生成二维码。 + +用法也很简单: + +```python +import qrcode + +# 二维码内容 +data = "http://www.zglg.work/wp-content/uploads/2020/10/image-3.png" +# 生成二维码 +img = qrcode.make(data=data) +# 直接显示二维码 +img.show() +# 保存二维码为文件 +img.save("我的微信.jpg") +``` + +生成的二维码如下: + +![](https://imgkr2.cn-bj.ufileos.com/f0b08c53-0107-483b-bbe5-072bebc58e8d.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=rVtaeBWhzLPPq%252BFCVtiOv6rS0tI%253D&Expires=1603544615) + + +大家微信扫描后,会出现我的二维码。 + +另外,还可以设置二维码的颜色等样式: + +```python +import qrcode + +# 实例化二维码生成类 +qr = qrcode.QRCode(border=2) +# 设置二维码数据 +data = "http://www.zglg.work/wp-content/uploads/2020/10/image-3.png" +qr.add_data(data=data) +# 启用二维码颜色设置 +qr.make(fit=True) +img = qr.make_image(fill_color="orange", back_color="white") + +# 显示二维码 +img.show() +``` + +生成一个orange的二维码: + +![](https://imgkr2.cn-bj.ufileos.com/cbd26fd8-27cf-4630-935f-6896822ce483.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=uy1r24x%252Fp5QpI5Wy10Ebdaz%252BpLM%253D&Expires=1603544681) + +更多样式,大家可以自己去玩耍。 + +## Python小项目:句子KWIC显示 + +上下文关键字(KWIC, Key Word In Context)是最常见的多行协调显示格式。 + +此小项目描述:输入一系列句子,给定一个给定单词,每个句子中至少会出现一次给定单词。目标输出,给定单词按照KWIC显示,KWIC显示的基本要求:待查询单词居中,前面`pre`序列右对齐,后面`post`序列左对齐,待查询单词前和后长度相等,若输入句子无法满足要求,用空格填充。 + +输入参数:输入句子sentences, 待查询单词selword, 滑动窗口长度`window_len` + +举例,输入如下六个句子,给定单词`secure`,输出如下字符串: + +```python + pre keyword post + + welfare , and secure the blessings of + nations , and secured immortal glory with + , and shall secure to you the + cherished . To secure us against these + defense as to secure our cities and + I can to secure economy and fidelity +``` + +请补充实现下面函数: + +```python +def kwic(sentences: List[str], selword: str, window_len: int) -> str: + """ + :type: sentences: input sentences + :type: selword: selected word + :type: window_len: window length + """ +``` + +更多KWIC显示参考如下: + +http://dep.chs.nihon-u.ac.jp/english_lang/tukamoto/kwic_e.html + +完整代码已经公布在:http://www.zglg.work/Python-20-topics/python-project1-kwic/ +![image](https://user-images.githubusercontent.com/20391209/123213609-c494dc00-d4f8-11eb-84d6-4d8caabb44f7.png) +![image](https://user-images.githubusercontent.com/20391209/123213901-26eddc80-d4f9-11eb-96cd-d3518005c4df.png) + diff --git a/md/198.md b/md/198.md index 566f6437..1a423780 100644 --- a/md/198.md +++ b/md/198.md @@ -6,6 +6,23 @@ @version @date 2020/04/05 ``` - +## 198 常见算术运算 -
[上一个例子](197.md) [下一个例子](199.md)
\ No newline at end of file +```python +x, y = 3, 2 +print(x + y) # = 5 +print(x - y) # = 1 +print(x * y) # = 6 +print(x / y) # = 1.5 +print(x // y) # = 1 +print(x % y) # = 1 +print(-x) # = -3 +print(abs(-x)) # = 3 +print(int(3.9)) # = 3 +print(float(x)) # = 3.0 +print(x ** y) # = 9 +``` + +大多数操作符都是不言自明的。注意,`//`运算符执行整数除法。结果是一个向下舍入的整数值(例如,3//2==1) + +
[上一个例子](197.md) [下一个例子](199.md)