假设你有如下数据:
姓名 |
科目 |
成绩 |
小黑 |
语文 |
42 |
小娜 |
语文 |
23 |
小白 |
语文 |
98 |
小乐 |
语文 |
52 |
小黑 |
数学 |
30 |
小娜 |
数学 |
76 |
小白 |
数学 |
47 |
小乐 |
数学 |
73 |
小黑 |
英语 |
63 |
小娜 |
英语 |
83 |
小白 |
英语 |
4 |
小乐 |
英语 |
71 |
想生成一个透视表,你肯定要去Excel工具里面操作,但是呢,数据多了可能会出错,所以我们可以借助python提供的 pandas 模块进行透视表生成。
视频演示
话不多说,我们先来看看生成后表格的数据项:
如上就是pandas生成的 Excel透视表,数据一目了然,看的非常清楚直观,前面两行和第四行是代码生成、便于拥有多个数据时进行区分的,如不需要,可进行删除,ALL是成绩的总和。
下面开始上代码教程
导入相关的模块:
import os
import pandas as pd
import numpy as np
import tkinter as tk
import tkinter.font as tkFont
from tkinter.filedialog import askdirectory
import tkinter.messagebox
import tkinter.filedialog
from tkinter import ttk
import threading as thr
from tkinter import scrolledtext
import datetime
然后新建一个GUI界面,并在GUI界面添加如下操作元素:两个输入框和两个按钮,分别用来选择需要操作的Excel表和选择保存的文件夹。还有另外三个输入框与按钮,分别用来选择显示在透视表左边和透视表上方的数据项,以及需要比较的数据项,最后是一个清除输入框内容的按钮和一个生成文件的按钮。
示例图如下:
好了,现在开始放代码
import os
import pandas as pd
import numpy as np
import tkinter as tk
import tkinter.font as tkFont
from tkinter.filedialog import askdirectory
import tkinter.messagebox
import tkinter.filedialog
from tkinter import ttk
import threading as thr
from tkinter import scrolledtext
import datetime
window = tk.Tk()
window.title('透视表生成(通用版)')
# 获取屏幕宽高
sw = window.winfo_screenwidth()
sh = window.winfo_screenheight()
# 设置屏幕的宽和高
ww = 600
wh = 500
x = (sw - ww) / 2
y = (sh - wh) / 2
# 根据屏幕宽高来让程序居中
window.geometry("%dx%d+%d+%d" % (ww, wh, x, y))
# 设置字体
fontStyle = tkFont.Font(family="Lucida Grande", size=15)
fontStyleRadio = tkFont.Font(family="Lucida Grande", size=13)
# 应用标题
tk.Label(window, text="透视表生成(通用版)", font=fontStyle).pack()
dataState = ""
ty = 0
# 设置frame容器
frame = tk.Frame(window, padx=3, pady=3)
frame.pack()
frame_left = tk.Frame(frame)
frame_right = tk.Frame(frame)
frame_left.pack(side='left')
frame_right.pack(side='right')
def selectFile():
if ty == 1:
tks2()
global paths
# 打开文件 设置只能打开xls和xlsx文件
paths = tkinter.filedialog.askopenfilename(title='打开表格',
filetypes=[("XLSX", ".xlsx"), ("XLS", ".xls"), ("CSV", ".csv")])
path.set(paths)
cc = paths.endswith(".xls")
global toCsv
# 进行表格数据读取,将第一行数据放入Listbox中进行后续选择
toCsv = 0
if cc:
df = pd.read_excel(paths)
else:
c2 = paths.endswith(".csv")
if c2:
toCsv = 1
df = pd.read_csv(paths, encoding='GBK')
else:
print("c2 ERROR!")
df = pd.read_excel(paths, engine='openpyxl')
labels = list(df.columns)
xl(labels)
global ttks
ttks = scrolledtext.ScrolledText(frame_left, width=30, height=9, font=("宋体", 15))
ttks.pack(padx=5, pady=10)
global lb
lb = tk.Listbox(frame_right, font=fontStyle, width=30, height=9, selectmode=tk.EXTENDED)
lb.pack(padx=5, pady=10)
for ite in labels:
lb.insert('end', ite)
lb.pack()
print(paths)
tks()
def sFile():
sfi = thr.Thread(target=selectFile, daemon=True, args=())
sfi.start()
# 如果第二次选择表格,就将ty设置为1,当ty=1时,清空 Listbox 的数据
def tks():
global ty
ty = 1
def tks2():
lb.destroy()
left1.destroy()
top1.destroy()
left1Title.destroy()
top1Title.destroy()
ttks.destroy()
ttks.pack_forget()
values2_.clear()
global values_
values2_ = []
# 要比较的数据
def lbs3():
for i in lb.curselection():
values2_.append(lb.get(i))
ttks.see(tk.END)
ttks.insert(tk.END, f'当前选中的数据:{str(values2_)}\n\n')
ttks.update()
def lbsc3():
for i in lb.curselection():
values2_.remove(lb.get(i))
ttks.see(tk.END)
ttks.insert(tk.END, f'当前选中的数据:{str(values2_)}\n\n')
ttks.update()
# 选择要保存的文件夹
def askd2():
global paths2
paths2 = askdirectory() + "/"
path2.set(paths2)
print(paths2)
# 选择文件和选择文件夹的输入框
path = tk.StringVar()
path2 = tk.StringVar()
tk.Entry(frame_left, textvariable=path, width=50).pack(padx=5, pady=10)
tk.Entry(frame_left, textvariable=path2, width=50).pack(padx=5, pady=10)
# 按钮
tk.Button(frame_right, text='选择文件', font=fontStyle, width=18, height=1, command=lambda: sFile()).pack(padx=3, pady=3)
tk.Button(frame_right, text='选择保存的文件夹', font=fontStyle, width=18, height=1, command=lambda: askd2()).pack(padx=3,
pady=3)
tk.Button(frame_left, text='删除数据', font=fontStyle, width=18, height=1, command=lambda: lbsc3()).pack(padx=3, pady=3)
tk.Button(frame_right, text='选中数据', font=fontStyle, width=18, height=1, command=lambda: lbs3()).pack(padx=3, pady=3)
def xl(labels):
global left1Title
global top1Title
left1Title = tk.Label(frame_left, text="左边第一列的数值", font=fontStyle)
top1Title = tk.Label(frame_right, text="顶部第一行的数值", font=fontStyle)
left1Title.pack()
top1Title.pack()
global left1
left1 = ttk.Combobox(frame_left, justify='center', state='readonly', width=17, value=labels)
left1.current(0)
left1.pack()
global top1
top1 = ttk.Combobox(frame_right, justify='center', state='readonly', width=17, value=labels)
top1.current(0)
top1.pack()
frame = tk.Frame(window, padx=3, pady=3)
frame.pack()
frame_left2 = tk.Frame(frame)
frame_right2 = tk.Frame(frame)
frame_left2.pack(side='left')
frame_right2.pack(side='right')
tk.Button(frame_left2, text='清空', font=fontStyle, width=15, height=1, command=lambda: qk()).pack(padx=3, pady=3)
tk.Button(frame_right2, text='开始转换', font=fontStyle, width=15, height=1, command=lambda: runTs()).pack(padx=3, pady=3)
# 清空内容的方法
def qk():
lb.destroy()
ttks.destroy()
ttks.pack_forget()
path.set('')
path2.set('')
values2_.clear()
left1.destroy()
top1.destroy()
left1Title.destroy()
top1Title.destroy()
# 关键方法!!! 透视表生成的方法
def ts(path, paths2):
print(values2_)
# ['期初含税金额', '期初未税金额', '期初税金', '销售数量']
# path 路径名 左边显示名称
# 首先检测选择的文件是xls还是xlsx,如果说xls就用xlrd读取文件,不用额外填写。
# 如果是xlsx,就要设置engine='openpyxl',使用openpyxl读取xlsx。 xlrd不能读取xlsx,openpyxl也不能读取xls文件。
cc = path.endswith(".xls")
if cc:
df = pd.read_excel(paths)
else:
c2 = paths.endswith(".csv")
if c2:
df = pd.read_csv(paths, encoding='GBK')
else:
df = pd.read_excel(paths, engine='openpyxl')
# index: 左边的数值 columns: 最上面的数值 values: 要比较的数值 aggfunc:有相同数据时相加 fill_value:空值设置为0 margins:求和
c1 = df.pivot_table(index=left1.get(), columns=[top1.get()], values=values2_, aggfunc=[np.sum], dropna=False,
fill_value=0, margins=True)
val_ = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
if toCsv == 1:
p11 = paths2 + val_ + '.csv'
c1.to_csv(p11, encoding='GBK')
else:
p11 = paths2 + val_ + '.xlsx'
c1.to_excel(p11)
ts = tkinter.messagebox.showinfo(title='提示', message="转换成功!")
if ts == 'ok':
os.startfile(p11)
def runTs():
rts = thr.Thread(target=ts, daemon=True, args=(paths, paths2))
rts.start()
window.mainloop()
如有不懂,可在下方提问。