分类目录归档:Python 办公

数据分析实战教程|Pandas处理数据太慢,来试试Polars吧!

很多人在学习数据分析的时候,肯定都会用到Pandas这个库,非常的实用!

从创建数据到读取各种格式的文件(text、csv、json),或者对数据进行切片和分割组合多个数据源,Pandas都能够很好的满足。

Pandas最初发布于2008年,使用Python、Cython和C编写的。是一个超级强大、快速易于使用的Python库,用于数据分析和处理。

当然Pandas也是有不足之处的,比如不具备多处理器,处理较大的数据集速度很慢。

今天,小F就给大家介绍一个新兴的Python库——Polars。

使用语法和Pandas差不多,处理数据的速度却比Pandas快了不少。

一个是大熊猫,一个是北极熊~

GitHub地址:https://github.com/ritchie46/polars

使用文档:https://ritchie46.github.io/polars-book/

Polars是通过Rust编写的一个库,Polars的内存模型是基于Apache Arrow。

Polars存在两种API,一种是Eager API,另一种则是Lazy API。

其中Eager API和Pandas的使用类似,语法差不太多,立即执行就能产生结果。

而Lazy API就像Spark,首先将查询转换为逻辑计划,然后对计划进行重组优化,以减少执行时间和内存使用。

安装Polars,使用百度pip源。

# 安装polars
pip install polars -i https://mirror.baidu.com/pypi/simple/

安装成功后,开始测试,比较Pandas和Polars处理数据的情况。

使用某网站注册用户的用户名数据进行分析,包含约2600万个用户名的CSV文件。

import pandas as pd

df = pd.read_csv(‘users.csv’)
print(df)

数据情况如下。

此外还使用了一个自己创建的CSV文件,用以数据整合测试。

import pandas as pd

df = pd.read_csv(‘fake_user.csv’)
print(df)

得到结果如下。

首先比较一下两个库的排序算法耗时。

import timeit
import pandas as pd

start = timeit.default_timer()

df = pd.read_csv(‘users.csv’)
df.sort_values(‘n’, ascending=False)
stop = timeit.default_timer()

print(‘Time: ‘, stop – start)

————————-
Time:  27.555776743218303

可以看到使用Pandas对数据进行排序,花费了大约28s。

import timeit
import polars as pl

start = timeit.default_timer()

df = pl.read_csv(‘users.csv’)
df.sort(by_column=‘n’, reverse=True)
stop = timeit.default_timer()

print(‘Time: ‘, stop – start)

———————–
Time:  9.924110282212496

Polars只花费了约10s,这意味着Polars比Pandas快了2.7倍。

下面,我们来试试数据整合的效果,纵向连接。

import timeit
import pandas as pd

start = timeit.default_timer()

df_users = pd.read_csv(‘users.csv’)
df_fake = pd.read_csv(‘fake_user.csv’)
df_users.append(df_fake, ignore_index=True)
stop = timeit.default_timer()

print(‘Time: ‘, stop – start)

————————
Time:  15.556222308427095

使用Pandas耗时15s。

import timeit
import polars as pl

start = timeit.default_timer()

df_users = pl.read_csv(‘users.csv’)
df_fake = pl.read_csv(‘fake_user.csv’)
df_users.vstack(df_fake)
stop = timeit.default_timer()

print(‘Time: ‘, stop – start)

———————–
Time:  3.475433263927698

Polars居然最使用了约3.5s,这里Polars比Pandas快了4.5倍。

通过上面的比较,Polars在处理速度上表现得相当不错。

可以是大家在未来处理数据时,另一种选择~

当然,Pandas目前历时12年,已经形成了很成熟的生态,支持很多其它的数据分析库。

Polars则是一个较新的库,不足的地方还有很多。

如果你的数据集对于Pandas来说太大,对于Spark来说太小,那么Polars便是你可以考虑的一个选择。

本文转载自公众号【法纳斯特】

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

利用 Python pyttsx3 将 PDF 文档转为语音音频

 

 

 

 

 

 

 

 

1. 转语音工具

微信读书里的电子书有配套的自动音频,而且声音优化的不错,比传统的机械朗读听起来舒服很多

记得之前看到过 Python有一个工具包,可以将文字转换为语音,支持英文和中文的同时,还能调节语速语调、导出 mp3 等音频文件

去 Github 查了下,这个依赖库叫:pyttsx3

简单来说,pyttsx3 可以文字转语音,且是离线工作的,这一点就很实用

安装比较容易,直接在命令行用 pip 安装:

pip install pyttsx3

我准备动手试试,将 PDF 书籍转成音频

用什么书呢?最近在群里看到有人发张磊的新作《价值》电子书,这本今年刚出的畅销书盗版猖獗,我之前在微信读书里看过,对作者长期主义的观点深信不疑

那就它了

2. PDF转文本

肯定需要先读取 PDF 中的文字,再利用 pyttsx3 转语音

Python 中操作 PDF 的工具库主要是 PyPDF2,但发现编码实在有点繁琐

我就换了另一个库 pdfplumber,它与 PyPDF2 语法类似,用起来还算流畅

pdfplumber 可以处理 PDF 包括文本、表格、格式在内的各种信息,小而强大

# 读取PDF文档
pdf = pdfplumber.open("价值.pdf")

# 获取页数
print("总页数:",len(pdf.pages))
print("-----------------------------------------")

# 读取第4页
first_page = pdf.pages[3]
print("本页:",first_page.page_number+1)
print("-----------------------------------------")

# 导出第4页文本
text = first_page.extract_text()
print(text)

输出文字内容如下:

上面的代码做了几件事情:「读取 PDF 文档、读取页数、读取第 4 页、输出第 4 页文本」

3. 文本转语音

接下来开始将第 4 页的文本转化为音频

import pyttsx3

# 初始化来获取语音引擎
engine = pyttsx3.init()

# 去掉文本中的换行符
text = text.replace('\n','')

# 朗读文本
engine.say(text)
engine.runAndWait()

上面代码使用 pyttsx3 将文本转化为音频,然后朗读出来

我是在 jupyter notebook 上做实验的,代码执行后,电脑会直接朗读

最后,将生成的音频保存为 mp3 格式

# 保存音频到本地,格式为mp3
engine.save_to_file(text, 'test.mp3')
engine.runAndWait()

当然,你还可以调整声音的类型、速度、大小

# 调整人声类型
voices = engine.getProperty('voices')  
engine.setProperty('voice', voices[0].id)

# 调整语速,范围一般在0~500之间
rate = engine.getProperty('rate')                         
engine.setProperty('rate'200)     

# 调整声量,范围在0~1之间
volume = engine.getProperty('volume')                         
engine.setProperty('volume',0.8

最后听下生成的语音是什么样的?

test.mp3 来自AirPython 01:58

整体比较流畅,虽然不如微信读书语音听着那般舒服,但做做小工具还是不错的

转自AirPython.

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Pandas 实战 — 结构化数据非等值范围查找

本文通过两个案例,三个部分介绍了如何在 pandas 结构化数据中进行高效的范围查找。

1.简单案例讲解

Pandas案例需求

有两张表,A表记录了很多款产品的三个基础字段,分别是产品ID,地区代码和重量:

B表是运费明细表,这个表结构很“业务”。每行对应着单个地区,不同档位重量,所对应的运费:

比如121地区,0-0.5kg的产品,运费是5.38元;2.01(实际应该是大于1)-3kg,运费则是5.44元。
现在,我们想要结合A表和B表,统计出A表每个产品付多少运费,应该怎么实现?
可以先自己思考一分钟!

解题思路

人海战术

任何数据需求,在人海战术面前都是弟弟。

A表一共215行,我们只需要找215个人,每个人只需要记好自己要统计那款产品的地区代码和重量字段,然后在B表中根据地区代码,找到所在地区运费标准,然后一眼扫过去,就能得到最终运费了。

两个“只需要”,问题就这样easy的解决了。

问题变成了,我还差214个人。

解构战术

通过人海战术,我们其实已经明确了解题的朴素思路:根据地区代码和重量,和B表匹配,返回运费结果。

难点在于,B表是偏透视表结构的,运费是横向分布,用Pandas就算用地区代码匹配,还是不能找到合适的运费区间。

怎么办呢?

如果我们把B表解构,变成“源数据”格式,问题就全部解决了:

转换完成后,和A表根据地区代码做一个匹配筛选,答案就自己跑出来了。
下面是动手时刻。

具体实现

先导入数据,A表(product):

B表(cost):

要想把B表变成“源数据”的格式,关键在于理解stack()堆叠操作,结合示例图比较容易搞懂:

通过stack操作,把多列变为单列多行,原本的2列数据堆成了1列,从而方便了一些场景下的匹配。要变回来也很简单,unstack即可:

在我们的具体场景中,先指定好不变的索引列,然后直接上stack:

这样,就得到了我们目标的源数据。接着,A表和B表做匹配:

值得注意的是,因为我们根据每个地方的重量区间做了堆叠,这里的匹配结果,每个产品保留了对应地区,所有重量区间的价格,离最终结果还有一步之遥。
需要把重量区间做拆分,从而和产品重量对比,找到对应的重量区间:

接着,根据重量的最低、最高区间,判断每一行的重量是否符合区间:

最后,筛选出符合区间的产品,及对应的价格等字段:

大功告成!

2.复杂一点的情况

Pandas案例需求

需求如下:

该问题最核心的解题思路是按照地区代码先将两张表关联起来,然后按照重量是否在指定的区间筛选出符合条件的记录。不同的解法实际区别也是,如何进行表关联,如何进行关联后的过滤。

上文的简化写法

简化后:

import pandas as pd

product = pd.read_excel('sample.xlsx', sheet_name='A')
cost = pd.read_excel('sample.xlsx', sheet_name='B')

fi_cost = cost.set_index(['地区代码','地区缩写']).stack().reset_index()
result = pd.merge(product, fi_cost, on='地区代码', how='left')
result.columns = ['产品ID''地区代码''重量''地区缩写''重量区间''价格']
result[['最低区间''最高区间']] = result['重量区间'].str.split('~', expand=True).astype(float)
result.query("最低区间<=`重量`<=最高区间")

顺序查找匹配

考虑到直接merge会产生笛卡尔积,多消耗N倍的内存,所以下面采用筛选连接法,执行耗时比merge连接稍微长点,但减少了内存消耗。

首先读取数据:

import pandas as pd
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = 'all'

product = pd.read_excel('sample.xlsx', sheet_name='A')
cost = pd.read_excel('sample.xlsx', sheet_name='B')

预览数据:

product.head()
cost.head()

下面我们将价格表由”宽格式”旋转为”长格式”方便匹配:

fi_cost = cost.melt(id_vars=["地区代码""地区缩写"], var_name="重量区间", value_name='价格')
fi_cost

观察价格区间0~0.5, 0.501~1, 1.01~2, 2.01~3, 3.01~4, 4.01~5, 5.01~7, 7.01~10, 10.01~15, 15.01~100000我们完全可以只取前面的数字或只取后面的数字,理解为一个前闭后开或前开后闭的区间,我取重量区间的最大值来表示区间:

fi_cost.重量区间 = fi_cost.重量区间.str.split("~").str[1].astype("float")
fi_cost.sort_values(["地区代码""重量区间"], inplace=True, ignore_index=True)
fi_cost.head(10)

测试对第一个产品,取出对应的地区价格表:

fi_cost_g = fi_cost.groupby("地区代码")
for product_id, area_id, weight in product.values:
    print(product_id, area_id, weight)
    cost_table = fi_cost_g.get_group(area_id)
    display(cost_table)
    break

下面我们继续测试根据重量筛选出对应的价格:

fi_cost_g = fi_cost.groupby("地区代码")[["地区缩写""重量区间""价格"]]
for product_id, area_id, weight in product.values:
    print(product_id, area_id, weight)
    cost_table = fi_cost_g.get_group(area_id)
    display(cost_table)
    for area, weight_cost, price in cost_table.values:
        if weight <= weight_cost:
            print(area, price)
            break
    break

可以看到已经顺利的匹配出对应的价格是20.05。

于是完善最终代码为:

result = []
fi_cost_g = fi_cost.groupby("地区代码")[["地区缩写""重量区间""价格"]]
for product_id, area_id, weight in product.values:
    cost_table = fi_cost_g.get_group(area_id)
    for area, weight_cost, price in cost_table.values:
        if weight <= weight_cost:
            break
    result.append((product_id, area_id, area, weight, price))
result = pd.DataFrame(result, columns=["产品ID""地区代码""地区缩写""重量(kg)""价格"])
result

成功匹配出每个产品对应的地区简写和价格。

顺序查找匹配的完整代码为:

import pandas as pd

product = pd.read_excel('sample.xlsx', sheet_name='A')
cost = pd.read_excel('sample.xlsx', sheet_name='B')

fi_cost = cost.melt(id_vars=["地区代码""地区缩写"], var_name="重量区间", value_name='价格')
fi_cost.重量区间 = fi_cost.重量区间.str.split("~").str[1].astype("float")
fi_cost.sort_values(["地区代码""重量区间"], inplace=True, ignore_index=True)
result = []
fi_cost_g = fi_cost.groupby("地区代码")[["地区缩写""重量区间""价格"]]
for product_id, area_id, weight in product.values:
    cost_table = fi_cost_g.get_group(area_id)
    for area, weight_cost, price in cost_table.values:
        if weight <= weight_cost:
            break
    result.append((product_id, area_id, area, weight, price))
result = pd.DataFrame(result, columns=["产品ID""地区代码""地区缩写""重量(kg)""价格"])
result

3.优化方案

前面两部分内容就已经解决了问题,考虑到上述区间查找其实是一个顺序查找的问题,所以我们可以使用二分查找进一步优化减少查找次数。

当然二分查找对于这种2位数级别的区间个数查找优化不明显,但是当区间增加到万级别,几十万的级别时,那个查找效率一下子就体现出来了,大概就是几万次查找和几次查找的区别。

字典查找+二分查找高效匹配

本次优化,主要通过字典查询大幅度加快了查询的效率,几乎实现了将非等值连接转换为等值连接。

首先读取数据:

import pandas as pd

product = pd.read_excel('sample.xlsx', sheet_name='A')
cost = pd.read_excel('sample.xlsx', sheet_name='B')
cost.head()

下面计划将价格表直接转换为能根据地区代码和索引快速查找价格的字典。

先取出区间范围列表,用于索引位置查找:

price_range = cost.columns[2:].str.split("~").str[1].astype("float").tolist()
price_range

结果:

[0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 7.0, 10.0, 15.0, 100000.0]

下面将测试二分查找的效果:

import bisect
import numpy as np

for a in np.linspace(0.5510):
    idx = bisect.bisect_left(price_range, a)
    print(a, idx)

结果:

0.5 0
1.0 1
1.5 2
2.0 2
2.5 3
3.0 3
3.5 4
4.0 4
4.5 5
5.0 5

可以打印索引列表方便对比:

print(*enumerate(price_range))

结果:

(0, 0.5) (1, 1.0) (2, 2.0) (3, 3.0) (4, 4.0) (5, 5.0) (6, 7.0) (7, 10.0) (8, 15.0) (9, 100000.0)

经过对比可以看到,二分查找可以正确的找到一个指定的重量在重量区间的索引位置。

于是我们可以构建地区代码和索引位置作联合主键快速查找价格的字典:

cost_dict = {}
for area_id, area, *prices in cost.values:
for idx, price in enumerate(prices):
        cost_dict[(area_id, idx)] = area, price

然后就可以批量查找对应的运费了:

result = []
for product_id, area_id, weight in product.values:
    idx = bisect.bisect_left(price_range, weight)
    area, price = cost_dict[(area_id, idx)]
    result.append((product_id, area_id, area, weight, price))
result = pd.DataFrame(result, columns=["产品ID""地区代码""地区缩写""重量(kg)""价格"])
result

字典查找+二分查找高效匹配的完整代码:

import pandas as pd
import bisect

product = pd.read_excel('sample.xlsx', sheet_name='A')
cost = pd.read_excel('sample.xlsx', sheet_name='B')
price_range = cost.columns[2:].str.split("~").str[1].astype("float").tolist()
cost_dict = {}
for area_id, area, *prices in cost.values:
for idx, price in enumerate(prices):
        cost_dict[(area_id, idx)] = area, price
result = []
for product_id, area_id, weight in product.values:
    idx = bisect.bisect_left(price_range, weight)
    area, price = cost_dict[(area_id, idx)]
    result.append((product_id, area_id, area, weight, price))
result = pd.DataFrame(result, columns=["产品ID""地区代码""地区缩写""重量(kg)""价格"])
result

两种算法的性能对比

可以看到即使如此小的数据量下依然存在几十倍的性能差异,将来更大的数量量时,性能差异会更大。

将非等值连接转换为等值连接

基于以上测试,我们可以将非等值连接转换为等值连接直接连接出结果,完整代码如下:

import pandas as pd
import bisect

product = pd.read_excel('sample.xlsx', sheet_name='A')
cost = pd.read_excel('sample.xlsx', sheet_name='B')
price_range = cost.columns[2:].str.split("~").str[1].astype("float").tolist()
cost.columns = ["地区代码""地区缩写"]+list(range(cost.shape[1]-2))
cost = cost.melt(id_vars=["地区代码""地区缩写"],
                       var_name='idx', value_name='运费')
product["idx"] = product["重量(kg)"].apply(
lambda weight: bisect.bisect_left(price_range, weight))
result = pd.merge(product, cost, on=['地区代码''idx'], how='left')
result.drop(columns=["idx"], inplace=True)
result

该方法的平均耗时为6ms:

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 实战教程 | 轻松实现APP自动化记账

前情回顾

不知道大家有没有手动记账的习惯,我大概从大学开始就坚持记账,中途也换过几个账本APP。目前使用的是圈子账本 ,它的记账界面如下图所示:

再说说我现在的情况,毕业之后支出越来越多越琐碎,每月的账单多到再手动记账有些过于浪费时间了。

不过有几点让我注意到了,似乎可以实现自动化记账:

一是我目前支出首选信用卡(支付宝、微信也绑定信用卡),几乎全部支出都在这里;

二是圈子账本可以通过上传模板文件来直接上传账单,现在也支持支付宝账单了;

三是我的支出类别比较单一,主要就下面几种:

折中方案

根据上面的几点,我搞了个折中的方案,并一直用到现在。

就是电脑登录信用卡官网,手动复制或者下载账单。

然后使用python调整成账本官网支持的格式,导出成excel格式,直接上传至官网。

下面给大家对比一下操作前和操作后的格式:

信用卡里的账单:

官网规定格式:

操作实战

先手动复制账单到excel里,先命名为测试数据.xlsx

然后我们开始使用python处理,导入数据

df = pd.read_excel('测试数据.xlsx',header = None)
df.head(6)

👆每隔三行其实是一条数据,所以我们要跳行提取数据

df2 = pd.DataFrame(columns=['日期','时间','入账金额','交易说明'])

df2['日期'] = df.iloc[[ i+1 for i in range(0,len(df),3)],[0]].reset_index()[0]
df2['时间'] = df.iloc[[ i+2 for i in range(0,len(df),3)],[0]].reset_index(drop=True)
df2['入账金额'] = df.iloc[[ i+1 for i in range(0,len(df),3)],[1]].reset_index(drop=True)
df2['交易说明'] = df.iloc[[ i+2 for i in range(0,len(df),3)],[1]].reset_index(drop=True)

创建了一个df2,并从df中隔行提取数据,结果如下

调整格式

下一步调整格式,先参考目标格式要求:

df2['时间'] = df2['日期'].apply(lambda x : str(x).replace('00:00:00','')) + df2['时间'].apply(lambda x : str(x)[:-3])
df2['入账金额'] = df2['入账金额'].str.lstrip('入账金额:¥')
df2['交易说明'] = df2['交易说明'].str.lstrip('交易说明:财付通公司-')
df2 = df2.drop(columns = '日期')

这样我们就调整好了时间入账金额(金额)交易说明(备注),还剩下一个关键的值就是类别,其实我自己的消费类别没几个,可以简单的利用交易说明判断类别,无法分辨的类别归为其他。

def fenlei(comment):
    if '美团' in comment or '拉扎斯'in comment:
        data = "餐饮"
    elif '花小猪'in comment or '滴滴'in comment:
        data = "交通"
    elif '燃气'in comment or '电费'in comment:
        data = "水电燃气"
    elif '拼多多'in comment:
        data = "生活用品"
    else:
        data = "其他"
    return data

利用上面的函数,就可以统计出类别这一列的值。

其中拉扎斯指的是饿了么,大家查一下自己账单就知道了。至于其他的种类,大家根据自己实际情况改改就可。

另外再添加另外两个固定列,就齐了。

df2['分类'] = df2.apply(lambda x :fenlei(x['交易说明']), axis=1)
df2 = df2[df2['入账金额'].astype(float) > 0]
df2['类型'] = '支出'
df2['账户'] = '交通银行'
df2.head()

👆这里面我还筛选了只大于0的入账金额,这是为了排除还款记录。

注:还款记录在信用卡账单里是负值

最后结果如下图所示:

导出数据

由于这次导出数据要固定格式,所以使用了openpyxl来向官网模板中直接写入,这样导入比较规范。

workbook = load_workbook(filename="朱小五.xlsx")
sheet = workbook.active
df2 = df2.iloc[:,[4,0,3,1,5,2]]
# 先删除第4行之后的旧数据,预计1000行完全够用
sheet.delete_rows(idx=2, amount=1000)
# 然后在进行添加数据
for row in df2.values.tolist():
    sheet.append(row)
    print(row)
print("已经保存到文件中")
workbook.save(filename="朱小五.xlsx")
workbook.close()

打开朱小五.xlsx,查看结果

没什么问题,将Excel导入账本官网中

完美导入
再打开手机记账APP
发现账单已经安安静静地躺在账本里啦!

本文转自快学Python.

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

同事要我帮忙补写178份Word日报!Python自动化办公实战教程

作者:Ryoko

来源:凹凸数据

不久前,一个同事有个项目要向领导交差,其中一部分工作是根据 excel 表中的每日数据,按格式整理成日报写入 word。

好家伙!足足 178 天的量要补,如果要靠复制粘贴,岂不是肝到吐血,(你给我自己解决啊!)

好吧ojbk,是时候祭出 Python 办公自动化了。

一、基础数据整理

首先让我们来看看数据样本和输出文档的需求(敏感数据已做和谐处理):原始 excel 文件中有 n 个子表,每个子表为一天的数据,存在无记录和有记录(部门数 ≥ 1,每个部门记录数 ≥ 1)两种情况,需分别整理成两种日报,一为纯文本描述,二为附带表格的文档。

撸起袖子,开骂!

哦不,开始写代码!

先将子表合成一个,便于统一观察每日数据记录的规律,也方便后期处理。使用 xlrd 库读表,获取工作簿中的活动表名,再使用 pandas 库遍历子表以合并,dataframe 格式的数据对 excel 表的相性绝佳。

def merge_sheet(filepath): # 合并多个同表头的子表
    wb = xlrd.open_workbook(filepath)
    sheets = wb.sheet_names()
    df_total = pd.DataFrame()
    for name in sheets:
        df = pd.read_excel(filepath, sheet_name=name)
        df_total = df_total.append(df)
    df_total.to_excel("merge.xlsx", index=False)

二、输出两种日报

(一)纯文本文档

根据需要输出的日报样式,输出无记录的日报只需读取【日期】列和【填报部门】列,将【填报部门】列为无的日期段按每日输出即可。观察原表数据,直接筛选无填报记录的数据丢到命名为“无”的子表里。

这里也可以利用 .groupby() 对【填报部门】列分组,取“无”的那一组,可是要注意一点:虽然 Python 很强大,但不需要将所有事情都交给 Python 做。

导入库和模块如下:

import pandas as pd
import xlrd
from docx import Document
from docx.shared import Pt
from docx.shared import Inches
from docx.oxml.ns import qn
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.section import WD_ORIENTATION

基本流程很简单,读入无填报记录的数据,按日期输出 word 文档。

def wu_to_word(filepath):
    df = pd.read_excel(filepath, sheet_name="无")
    date_list = list(df['日期'])
    for d in date_list:
        filename = wordname+str(d)+").docx"  # 输出的word文件名
        title = "("+str(d)[:4]+"."+str(d)[4:6]+"."+str(d)[6:8]+")"  # 副标题日期XXXX.XX.XX
        word = str(d)[:4]+"年"+str(d)[4:6]+"月"+str(d)[6:8]+"日"  # 开头、落款日期XXXX年XX月XX日
        wu_doc(title, word, filename)
        print(f"文件:{filename},{title},{word} 已保存")

每份文档都会用到的同样的内容也可以先设定好。

wordname = “XX公司业务数据表(日报”
all_title = ” XX公司业务报告”

生成 word 内容,不加表格的情况下还是比较容易实现的,注意调整好格式。

def wu_doc(title,word,filename):  # 传入副标题日期,文段开头及落款的日期,文件名
    doc = Document()  # 创建文档对象
    section = doc.sections[0]  # 获取页面节点
section.orientation = WD_ORIENTATION.LANDSCAPE  # 页面方向设为横版
new_width, new_height = section.page_height, section.page_width  # 将原始长宽互换,实现将竖版页面变为横版
    section.page_width = new_width
    section.page_height = new_height
    # 段落的全局设置
    doc.styles['Normal'].font.name = u'宋体'  # 字体
    doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')  # 中文字体需再添加这个设置
    doc.styles['Normal'].font.size = Pt(14)  # 字号 四号对应14
    t1 = doc.add_paragraph()  # 添加一个段落
    t1.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER  # 居中
    _t1 = t1.add_run(all_title)  # 添加段落内容(大标题)
    _t1.bold = True  # 加粗
    _t1.font.size = Pt(22)
    t2 = doc.add_paragraph()  # 再添加一个段落
    t2.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER  # 居中
    _t2 = t2.add_run(title + "\n")  # 添加段落内容(副标题)
    _t2.bold = True
    doc.add_paragraph(word + "无记录。\n\n").paragraph_format.first_line_indent = Inches(0.35)  # 添加段落同时添加内容,并设置首行缩进
    doc.add_paragraph(word).paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT  # 落款日期右对齐
    doc.save(dir+filename)  # 按路径+文件名保存

执行!104 份无填报记录的日报就写好啦,干脆就这样交差吧,剩下的不想研究了哈哈哈。

(二)附表格文档

有报送记录的数据处理起来相对复杂一点,先看一下原始数据。

比如 X 年 X 月 X 日,有 N 个部门填报了数据,根据文档样例,文段描述部分需要整理成如下格式:

部门 A:“报送内容1” X条记录;“报送内容2” Y条记录;部门 B:……;部门 C:……;

而附件表格部分需整理成如下格式,可以预想把每一行需要的数据整理一个 list,按行写入表格:

一级指标 二级指标 三级指标 四级指标 各部门报送情况 备注
lalala hahaha balabala 若为空则沿用上级 部门A:报送内容1 有记录未上传,没报,系统崩了
aaa bbb ccc ddd 部门A:报送内容2 已上传,报的好
部门B:报送内容1

基本流程类似,读表后先按日期分组,每一组含一天中的一个或多个部门数据,再生成某一天的附件需要的表格,接着整理文段描述,最后按日期输出每一天的 word 文档。

def what_to_word(filepath):
    df = pd.read_excel(filepath, sheet_name="有")
    df.fillna('', inplace=True)  # 替换nan值为空字符
    dates = []  # 日期列表
    df_total = []  # 分日期存的所有df
    list_total = []  # 每一份word中需要的表数据合集
    for d in df.groupby('日期'):
        dates.append(d[0])
        df_total.append(d[1])
    for index,date in enumerate(dates):
        list_oneday = []  # 某一个word所需的表数据
        for row in range(len(df_total[index])):
            list_row = get_table_data(df_total, index, row)  # 其中一行数据
            list_oneday.append(list_row)
        list_total.append(list_oneday)
    for index, date in enumerate(dates):
        filename = wordname+str(date)+").docx"  # 输出的word文件名
        title = "("+str(date)[:4]+"."+str(date)[4:6]+"."+str(date)[6:8]+")"  # 副标题日期XXXX.XX.XX
        word = str(date)[:4]+"年"+str(date)[4:6]+"月"+str(date)[6:8]+"日"  # 开头、落款日期XXXX年XX月XX日
        sentence = get_sentence(df_total, index)  # 某一天的文段描述
        what_doc(title, word, sentence, list_total[index], filename)  #传入需要的内容后输出文档
        print(f"文件:{filename} 已保存")

下面让我们分别看看整理表格、整理文段、输出文档是如何实现的。

1、整理表格

获取 excel 表中的一行数据(说明df_total[df_index] 为一个 dataframe,其 values 为一个二维的 numpy 数组),整理各级指标、各部门报送情况和备注,返回一个列表。

def get_table_data(df_total, df_index, table_row):
    list1 = df_total[df_index].values[table_row]  # excel表中的一行
    list2 = list1[3:7]  # 一至四级指标
    for i in range(len(list2)):  # 当前指标为空则沿用上级指标
        if list2[i] == '空' and i != 0:
            list2[i] = list2[i - 1]
    content = list1[2] + ":\n" + list1[-4]  # 报送内容
    if '否' in list1[-2]:  # 备注
        remark = '有记录未上传,' + str(list1[-1])
    else:
        remark = '已上传'
    list3 = list2.tolist()  # 需填入word中的表数据,由numpy数组转为list列表
    list3.append(str(content))
    list3.append(str(remark))
    return list3

2、整理文段

对当日数据中的【填报部门】列中的唯一值计数,得知有 N 个部门填报了数据。对部门分组,获取其相关信息,组合成 [(报送内容,记录数,是否上报,备注)] 的格式,再整理出形如 “有N个部门报送了数据:部门X:“ 报送内容XXX ” X条记录;… …” 的描述串。

def get_sentence(df_total, df_index):
    df_oneday = df_total[df_index]
    num = df_oneday['填报部门'].nunique()  # 部门的数量
    group = []  # 部门名称
    detail = []  # 组合某个部门的数据,其中元素为元组格式(, , , )
    info = ''  # 报送情况描述
    for item in df_oneday.groupby('填报部门'):
        group.append(item[0])
        detail.append(
            list(
                zip(
                    list(item[1]['报送内容']),
                    list(item[1]['记录数']),
                    list(item[1]['是否上报']),
                    list(item[1]['备注'])
                )
            )
        )
    for index, g in enumerate(group):  # 整理每个部门的填报情况
        mes = str(g)+':'  # 部门开头
        for i in range(len(detail[index])):
            _mes = detail[index][i]
            if int(_mes[1])>0:
                mes = mes + f'“{_mes[0]}{_mes[1]}条记录;'
        info = info + mes
    info = info[:-1]+"。"  #将最后一个分号替换成句号
    sentence = f"有{num}个部门报送了数据:{info}"
    return sentence

3、输出文档

(耐心警告!)调整 word 中的文本和表格样式的操作比较繁琐,需一步一步设置,预设表头如下:

table_title = [‘一级指标’, ‘二级指标’, ‘三级指标’, ‘四级指标’, ‘各部门报送情况’, ‘备注’]

其他详见代码注释。

def what_doc(title, word, sentence, table, filename):  # 传入副标题日期,开头/落款日期,文段,表数据,文件名
    doc = Document()
    section = doc.sections[0]
    new_width, new_height = section.page_height, section.page_width
    section.orientation = WD_ORIENTATION.LANDSCAPE
    section.page_width = new_width
    section.page_height = new_height
    # 段落的全局设置
    doc.styles['Normal'].font.name = u'宋体'  # 字体
    doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')  # 中文字体需再添加这个设置
    doc.styles['Normal'].font.size = Pt(14)  # 字号 四号对应14
    t1 = doc.add_paragraph()  # 大标题
    t1.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER  # 居中
    _t1 = t1.add_run(all_title)
    _t1.bold = True
    _t1.font.size = Pt(22)
    t2 = doc.add_paragraph()  # 副标题
    t2.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER  # 居中
    _t2 = t2.add_run(title + "\n")
    _t2.bold = True
    doc.add_paragraph(word + sentence +"\n\n").paragraph_format.first_line_indent = Inches(0.35)  # 首行缩进
    doc.add_paragraph(word).paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT  # 右对齐
    doc.add_paragraph("各部门具体报送情况见附件:")

    doc.add_page_break()  # 分页---------------------------------------------------------------
    fujian = doc.add_paragraph().add_run("\n附件")
    fujian.bold = True
    fujian.font.size = Pt(16)
    t3 = doc.add_paragraph()  # 附件大标题
    t3.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER  # 居中
    _t3 = t3.add_run("XX公司业务数据表")
    _t3.bold = True
    _t3.font.size = Pt(22)

    rows = len(table)+1
    word_table = doc.add_table(rows=rows, cols=6, style='Table Grid')  # 创建rows行、6列的表格
    word_table.autofit=True  # 添加框线
    table = [table_title] + table  # 固定的表头+表数据
    for row in range(rows):  # 写入表格
        cells = word_table.rows[row].cells
        for col in range(6):
            cells[col].text = str(table[row][col])
    for i in range(len(word_table.rows)):    # 遍历行列,逐格修改样式
        for j in range(len(word_table.columns)):
            for par in word_table.cell(i, j).paragraphs:  # 修改字号
                for run in par.runs:
                    run.font.size = Pt(10.5)
            for par in word_table.cell(0, j).paragraphs:  # 第一行加粗
                for run in par.runs:
                    run.bold = True
    doc.save(dir+filename)

执行!74份有记录的日报也写好啦,一共178份。

一顿操作猛如虎,总算是批量生成了日报,盒饭该加个鸡腿子了吧… …

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

实战教程!用 Python 制作抖音爆款视频!

前几天在抖音上刷到一个慢慢变老的视频,播放量居然有 30W+,当时就在想这视频 Python 可不可以做?

经过一番搜索,小编找到了腾讯云的人脸年龄变化 API,上面介绍说只要用户上传一张人脸图片,基于人脸编辑与生成算法,就可以输出一张人脸变老或变年轻的图片,并支持实现人脸不同年龄的变化

准备工作

获取 API 秘钥

第一步,在注册账号之后,打开 API 密钥管理页面(https://console.cloud.tencent.com/cam/capi)获取到 SecretId 和 SecretKey。

第二步,安装腾讯云的 SDK

pip3 install tencentcloud-sdk-python

人脸属性

在人脸年龄变化 API 中有一个 AgeInfo 参数,它包含了 Age 和 FaceRect 两个属性,其中 FaceRect 属性必须填人脸在照片中基于左上角的 X、Y 坐标和人脸的高度与宽度。所以先要调用人脸检测与分析 API 得到这些数据。

下面的示例图是在百度图片中截取的。

import json
import base64
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.iai.v20200303 import iai_client
from tencentcloud.iai.v20200303 import models as models03

sid = "xxx"
skey = "xxx"
try

    filepath = '/Users/imeng/Downloads/face/face.png'
    file = open(filepath, "rb")
    base64_data = base64.b64encode(file.read())

    cred = credential.Credential(sid, skey) 
    httpProfile = HttpProfile()
    httpProfile.endpoint = "iai.tencentcloudapi.com"

    clientProfile = ClientProfile()
    clientProfile.httpProfile = httpProfile
    client = iai_client.IaiClient(cred, "ap-beijing", clientProfile) 

    req = models03.DetectFaceAttributesRequest()
    params = {
        "MaxFaceNum":2,
        "Action":"DetectFace",
        "Version":"2018-03-01",
        "Image": base64_data.decode()
    }
    req.from_json_string(json.dumps(params))
    resp = client.DetectFaceAttributes(req) 

    faceDetailInfos = resp.FaceDetailInfos
    for faceDetailInfo in faceDetailInfos:
        faceRect = faceDetailInfo.FaceRect
        print(faceRect)
except TencentCloudSDKException as err: 
    print(err) 

示例结果

{"X"62"Y"13"Width"145"Height"230}
{"X"426"Y"113"Width"115"Height"139}

修改年龄

在上面已经得到了各个人脸的 X、Y、Width、Height 属性,加上变老的年龄 Age,就可以请求年龄变化 API 了。

这里需要注意的是 models 模块,人脸检测 models 模块是在 tencentcloud.iai.v20200303 包下,人脸年龄变化的 models 是在 tencentcloud.ft.v20200304 下,两个 models 模块并不兼容。

import json
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ft.v20200304 import ft_client, models

cred = credential.Credential(sid, skey) 
httpProfile = HttpProfile()
httpProfile.endpoint = "ft.tencentcloudapi.com"
clientProfile.httpProfile = httpProfile
client = ft_client.FtClient(cred, "ap-beijing", clientProfile) 

req = models.ChangeAgePicRequest()

for age in range(7080):
params = {
    "Image": base64_data.decode(),
    "AgeInfos": [
        {
            "Age": age,
            "FaceRect": {
                "Y": faceDetailInfos[0].FaceRect.Y,
                "X": faceDetailInfos[0].FaceRect.X,
                "Width": faceDetailInfos[0].FaceRect.Width,
                "Height": faceDetailInfos[0].FaceRect.Height
            } 
        },
        {
            "Age": age,
            "FaceRect": {
                "Y": faceDetailInfos[1].FaceRect.Y,
                "X": faceDetailInfos[1].FaceRect.X,
                "Width": faceDetailInfos[1].FaceRect.Width,
                "Height": faceDetailInfos[1].FaceRect.Height
            } 
        }
    ],
    "RspImgType""base64"
}
req.from_json_string(json.dumps(params))
resp = client.ChangeAgePic(req) 
image_base64 = resp.ResultImage
image_data = base64.b64decode(image_base64)
file_path = '/Users/imeng/Downloads/face/{}.png'.format(age)
with open(file_path, 'wb'as f:
    f.write(image_data)
time.sleep(1)

示例结果

最后的视频可以将图片一张一张插入 PPT 幻灯片,点击保存为视频。

转自AirPython.

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典