1. 程式人生 > >python實現Excel檔案讀取的程式(附原始碼)

python實現Excel檔案讀取的程式(附原始碼)

python實現Excel檔案讀取的程式

  前一段時間幫一個朋友用python寫了一個讀Excel程式操作的程式,具體要求為:讀取兩個Excel檔案,根據其中某個特徵的特徵值對這兩個檔案進行取交集操作,生成三個Excel檔案,第一個Excel檔案為這兩個檔案的公共集,第二個Excel檔案為第一個Excel檔案除去公共項後的Excel檔案,第三個Excel檔案為第二個Excel檔案除去公共項後的Excel檔案。
  最近在做一個銷售價格預測的專案,資料量也是百萬級別,在做這個專案的過程中又重新認識了一下python中大資料處理神器pandas模組,發現pandas對於表格類資料的處理效率非常高(以前對於pandas的理解只是停留在是一個模組包,和array、list、directory型別類似只是多了一些方法,所以不以為然,現在發現他對於表格處理內含的函式真的很多和很強大,對於大量資料的處理效率真的很高,特別適合資料探勘領域中的特徵處理),於是乎想著將這個Excel檔案處理程式用pandas模組進行改進,結果效果和預期一樣,之前用xlrd和xlsxwriter模組寫的程式拿一個數據量30w+和一個數據量50w+的兩張表格做測試耗時兩個小時多(後文有介紹),現在換成pandas模組時間僅用不到五分鐘(不可思議),主要耗時在與檔案讀取和寫入,主要處理思想如下:
  (1)將兩表內要比較的列匯出來並轉換為set型別
  (2)對兩個set集合取交集,然後轉換為list型別
  (3)用pandas內建的方法將兩表交集內的行找出來,並匯入一張新表內。
  (4)依次求出兩表和其交集的差集,並匯入到新表內。
  總結:對於大量資料的處理,pandas就是一款神器,其功能強大,效率比自己寫的for迴圈要高很多。
程式實現如下

點選下載

打包成單個可執行檔案命令:installer -F MainInterface.py -p ExcelDealFunc.py --hidden-import ExcelDealFunc.py

# -*- coding: utf-8 -*-

import pandas as pd
import time

def exceldealfunc(filename1,filename2,filename3,filename4,filename5,str1,str2):
    """
        函式名:exceldealfunc(filename1,filename2,filename3,filename4,filename5,num1,num2)
        函式功能:執行表1表2取公共部分,並生成公共集表3,表1的去除公共集的表4,表2的去除公共集的表5
            輸入	1: filename1:讀取表1的檔案路徑
            輸入	1: filename2:讀取表2的檔案路徑
            輸入	1: filename3:寫入表3的檔案路徑
            輸入	1: filename4:寫入表4的檔案路徑
            輸入	1: filename5:寫入表5的檔案路徑
            輸入	2: str1:表1要比較的列號名
            輸入	2: str2:表2要比較的列號名
            輸出	1: 無
        其他說明:無
    """
    start=time.time()

    table1=pd.read_excel(filename1)     # 讀取表1
    print("Time:%d s,Read Table1 Successful!"%(time.time()-start))
    table2=pd.read_excel(filename2)     # 讀取表2
    print("Time:%d s,Read Table2 Successful!"%(time.time()-start))
    print()

    set1=set(table1[str1])              # 將表1要比較的列轉換為集合格式(集合1)
    print("Time:%d s,File1 turn into a set Successful!"%(time.time()-start))
    set2=set(table2[str2])              # 將表2要比較的列轉換為集合格式(集合2)
    print("Time:%d s,File2 turn into a set Successful!"%(time.time()-start))
    print()

    set3=set1 & set2                    # 取集合1和集合2的交集set3
    print("Time:%d s,set3 = set1 & set2 Successful!"%(time.time()-start))
    list3=list(set3)                    # 將set3轉換為列表的格式
    print("Time:%d s,set3 turn to a list Successful!"%(time.time()-start))
    table1[table1[str1].isin(list3)].to_excel(filename3,index=False,)   # 將交集儲存到表3
    print("Time:%d s,Write to Table3 Successful!"%(time.time()-start))
    print()

    list4=list(set1 - set3)             # 取集合1和交集的差集,並轉換為列表格式
    print("Time:%d s,(set1 - set3) turn to a list Successful!"%(time.time()-start))
    table1[table1[str1].isin(list4)].to_excel(filename4,index=False,)   # 將差集1儲存到表4
    print("Time:%d s,Write to Table4 Successful!"%(time.time()-start))
    print()

    list5=list(set2 - set3)             # 取集合2和交集的差集,並轉換為列表格式
    print("Time:%d s,(set2 - set3) turn to a list Successful!"%(time.time()-start))
    table2[table2[str2].isin(list5)].to_excel(filename5,index=False,)   # 將差集2儲存到表5
    print("Time:%d s,Write to Table5 Successful!"%(time.time()-start))
    print()

    print("All task finish! Using Time:%d s."%(time.time()-start))

*********分割線 以下是改進前採用xlrd和xlsxwriter模組對Excel檔案進行處理,這裡僅供參考*********

  由於朋友所需要處理的資料量比較大,給我的是一個30w的資料集和一個50w的資料集小樣本,這裡記錄一下我碰到的兩個坑:
  (1)xlwt模組儲存的檔案格式是xls,最多隻能夠寫入65535行列256列,如果行數或列數過多就會報錯。解決方法有兩種,第一種是繼續使用該模組,每65535行後或者256列後就新建一張sheet,將超過的資料儲存到新sheet中;第二種方法是使用xlsxwriter模組,xlsxwriter模組儲存的檔案格式是xlsx,能夠最多寫入1048576行65535列,如果資料量還是過大隻能用在該基礎上再用第一種方法了。筆者用的是第二種。
  (2)python列表中對列表直接操作速度會明顯快於對列表索引操作,所以變成實現時儘量多用python內建列表操作(如a in list或者list=list1&list2等等),筆者一開始為了變成方便在其中用了列表索引操作,結果從晚上6點半執行到晚上11半都沒出結果,第二天早上來實驗室才發現執行完畢,將程式碼中對列表索引操作改成了對list操作後再次執行,結果只運行了兩個小時。

  程式介面說明:(後文附原始碼與GitHub連結)
在這裡插入圖片描述
  這款軟體只要選好兩個Excel檔案,以及對應要比較的列號(從0開始計數),然後設定好要存放生成的Excel檔案地址與檔名然後直接點選開始就可以了。
  重置按鈕只是在執行完一次後啟用開始按鈕的作用。
筆者電腦配置如下:
在這裡插入圖片描述
  測試了資料量分別為50w+和30w+的兩張表,用時是2個小時左右。

  下面直接pose出原始碼:
GitHub下載地址:MyGitHub連結
檔案1:GUI視窗

# -*- coding: utf-8 -*-
from tkinter import *
from tkinter.filedialog import askdirectory
from tkinter.filedialog import askopenfilename
from ExcelDealFunc import exceldealfunc
import os

class MyMainFace(object):
    """主介面類"""
    def __init__(self):
        """
            函式名:__init__(self)
            函式功能:MyMainFace類的建構函式,介面元件均在此構造出的
                輸入	1: MyMainFace類的物件,自身,無需輸入
                輸出	1: 無
            其他說明:無
        """
        self.root = Tk()
        self.root.title('Excel處理程式')

        # 請選擇目標路徑
        Label(self.root,text = "請選擇目標路徑:",fg="red").grid(row = 0, column = 0,sticky="w")

        Label(self.root,text = "表1目標路徑:").grid(row = 1, column = 0,sticky="e")
        self.path1 = StringVar()
        Entry(self.root, width=60,textvariable = self.path1,state='readonly').grid(row = 1, column = 1,columnspan=3)
        Button(self.root, text = "路徑選擇", command = self.selectPath1).grid(row = 1, column = 4)
        self.num1 = StringVar()
        Label(self.root,text = "  比較欄位的ID").grid(row = 1, column = 5,sticky="e")
        Entry(self.root, width=5,textvariable = self.num1).grid(row = 1, column = 6)

        Label(self.root,text = "表2目標路徑:").grid(row = 2, column = 0,sticky="e")
        self.path2 = StringVar()
        Entry(self.root,width=60,textvariable = self.path2,state='readonly').grid(row = 2, column = 1,columnspan=3)
        Button(self.root, text = "路徑選擇", command = self.selectPath2).grid(row = 2, column = 4)
        self.num2 = StringVar()
        Label(self.root,text = "  比較欄位的ID").grid(row = 2, column = 5,sticky="e")
        Entry(self.root, width=5,textvariable = self.num2).grid(row = 2, column = 6)

        # 請選擇生成表格路徑
        Label(self.root,text = "請選擇生成表格路徑:",fg="red").grid(row = 4, column = 0,sticky="w")

        Label(self.root,text = "表格存放路徑:").grid(row = 5, column = 0,sticky="e")
        self.path3 = StringVar()
        Entry(self.root,width=60,textvariable = self.path3,state='readonly').grid(row = 5, column = 1,columnspan=3)
        Button(self.root, text = "路徑選擇", command = self.selectPath3).grid(row = 5, column = 4)

        # 請輸入生成表格名稱
        Label(self.root,text = "請輸入生成表格名稱:",fg="red").grid(row = 6, column = 0,sticky="w")

        Label(self.root,text = "表1表2的交集表的表名:").grid(row = 7, column = 0,sticky="e")
        self.name1 = StringVar()
        Entry(self.root, textvariable = self.name1).grid(row = 7, column = 1,sticky="w")

        Label(self.root,text = "表1去除交集後的表名:").grid(row = 8, column = 0,sticky="e")
        self.name2 = StringVar()
        Entry(self.root, textvariable = self.name2).grid(row = 8, column = 1,sticky="w")

        Label(self.root,text = "表2去除交集後的表名:").grid(row = 9, column = 0,sticky="e")
        self.name3 = StringVar()
        Entry(self.root, textvariable = self.name3).grid(row = 9, column = 1,sticky="w")

        self.labeltxt=StringVar()
        self.labeltxt.set(" ")
        Label(self.root,textvariable = self.labeltxt,fg="red").grid(row = 7, column = 3)
        Label(self.root,text = "以表1為參考",fg="red").grid(row = 7, column = 2)

        self.var = StringVar()
        self.var.set("開始")
        self.button =  Button(self.root,textvariable = self.var,command = self.start, width = 5)
        self.button.grid(row = 8,column = 2,padx = 5)

        self.var2 = StringVar()
        self.var2.set("重置")
        self.button2 =  Button(self.root,textvariable = self.var2,command = self.reset, width = 5)
        self.button2.grid(row = 9,column = 3,padx = 5)

        # 建立一個背景色為白色的矩形
        self.canvas = Canvas(self.root,width = 170,height = 26,bg = "white")
        # 建立一個矩形外邊框(距離左邊,距離頂部,矩形寬度,矩形高度),線型寬度,顏色
        self.out_line = self.canvas.create_rectangle(2,2,180,27,width = 1,outline = "black") 
        self.canvas.grid(row = 8,column = 3,ipadx = 5)

        self.root.mainloop()

    def start(self):
        """
            函式名:start(self)
            函式功能:開始按鈕的功能函式
                輸入	1: MyMainFace類的物件,自身
                輸出	1: 無
            其他說明:無
        """
        if self.path1.get():
            filename1=self.path1.get()
        else:
            self.labeltxt.set("請選擇好表1")
            return

        if self.path2.get():
            filename2=self.path2.get()
        else:
            self.labeltxt.set("請選擇好表2")
            return

        if self.path3.get():
            filename=self.path3.get()
        else:
            filename="data"
        
        if self.name1.get():
            filename3=filename+"/"+self.name1.get()+".xlsx"
        else:
            filename3=filename+"/Table1_Table2.xlsx"

        if self.name2.get():
            filename4=filename+"/"+self.name2.get()+".xlsx"
        else:
            filename4=filename+"/Table1_del.xlsx"

        if self.name3.get():
            filename5=filename+"/"+self.name3.get()+".xlsx"
        else:
            filename5=filename+"/Table2_del.xlsx"

        if self.num1.get():
            num1=int(self.num1.get())
        else:
            self.labeltxt.set("請選擇好num1")
            return

        if self.num2.get():
            num2=int(self.num2.get())
        else:
            self.labeltxt.set("請選擇好num2")
            return

        self.button.config(state="disable") # 關閉按鈕1功能
        self.root.withdraw()
        os.system('cls')
        print("正在執行中請稍等...")
        exceldealfunc(filename1,filename2,filename3,filename4,filename5,num1,num2)
        self.root.deiconify()
        self.labeltxt.set("Finally")

    def scheduleshow(self,i):
        fill_line = self.canvas.create_rectangle(2,2,0,27,width = 0,fill = "blue") 
        self.canvas.coords(fill_line, (0, 0, 180*i, 30))
        self.var.set(str(round(100*i,1))+"%")
        self.root.update()

    def reset(self):
        """
            函式名:reset(self)
            函式功能:重置按鈕的功能函式
                輸入	1: MyMainFace類的物件,自身
                輸出	1: 無
            其他說明:無
        """
        self.button.config(state="active") # 啟用按鈕1

        fill_line = self.canvas.create_rectangle(2,2,0,27,width = 0,fill = "white") 
        self.var.set("開始")
        self.labeltxt.set(" ")
        self.canvas.coords(fill_line, (0, 0, 181, 30))
        self.root.update()

    def selectPath1(self):
        """
            函式名:selectPath1(self)
            函式功能:選擇路徑1按鈕的功能函式
                輸入	1: MyMainFace類的物件,自身
                輸出	1: 無
            其他說明:無
        """
        path_ = askopenfilename(filetypes = [('Excel', '*.xls*')])
        self.path1.set(path_)

    def selectPath2(self):
        """
            函式名:selectPath2(self)
            函式功能:選擇路徑2按鈕的功能函式
                輸入	1: MyMainFace類的物件,自身
                輸出	1: 無
            其他說明:無
        """
        path_ = askopenfilename(filetypes = [('Excel', '*.xls*')])
        self.path2.set(path_)

    def selectPath3(self):
        """
            函式名:selectPath3(self)
            函式功能:選擇路徑3按鈕的功能函式
                輸入	1: MyMainFace類的物件,自身
                輸出	1: 無
            其他說明:無
        """
        path_ = askdirectory()
        self.path3.set(path_)

##############################程式入口#########################################
if __name__=="__main__":
    MyMainFace()

檔案2:Excel檔案處理模組

# -*- coding: utf-8 -*-
import xlrd,xlsxwriter
import sys

def writeoneline(table_from,table_to,line,nrows):
    """
        函式名:writeoneline(table_from,table_to,line,nrows)
        函式功能:讀取table_from的第line行資料寫入到table_to的第nrows行
            輸入1: table_from:從該表內讀取要寫入的資料
            輸入2: table_to:向該表內新增一行資料
            輸入3: line:table_from內要讀取資料的行數
            輸入4: nrows:table_to內要寫入資料的行數
            輸出1: 無
        其他說明:無
    """
    write_data=table_from.row_values(line)
    for i in range(len(write_data)):
        table_to.write(nrows,i,write_data[i])

def readdata(filename,num):
    """
        函式名:readdata(filename,num)
        函式功能:通過檔案路徑,開啟Excel檔案,讀取sheet
            輸入	1: filename:讀取表的檔案路徑
            輸入	2: num:要比較的列號
            輸出	1: table:sheet表資訊
            輸出	2: nrows:該Excel表的行數
            輸出	3: count:count要比較列的資料列表
        其他說明:無
    """
    data = xlrd.open_workbook(filename)
    table = data.sheets()[0]
    nrows = table.nrows
    count=table.col_values(num)

    return table,nrows,count

def exceldealfunc(filename1,filename2,filename3,filename4,filename5,num1,num2):
    """
        函式名:exceldealfunc(filename1,filename2,filename3,filename4,filename5,num1,num2)
        函式功能:執行表1表2取公共部分,並生成公共集表3,表1的去除公共集的表4,表2的去除公共集的表5
            輸入	1: filename1:讀取表1的檔案路徑
            輸入	1: filename2:讀取表2的檔案路徑
            輸入	1: filename3:寫入表3的檔案路徑
            輸入	1: filename4:寫入表4的檔案路徑
            輸入	1: filename5:寫入表5的檔案路徑
            輸入	2: num1:表1要比較的列號
            輸入	2: num2:表2要比較的列號
            輸出	1: 無
        其他說明:無
    """
    table1,nrows1,count1=readdata(filename1,num1)
    print("Read Table1 Successful!")
    table2,nrows2,count2=readdata(filename2,num2)
    print("Read Table2 Successful!")

    data3 = xlsxwriter.Workbook(filename3)    # 共同使用者樣本
    table3 = data3.add_worksheet()

    data4 = xlsxwriter.Workbook(filename4)    # Table1處理後的樣本Table4
    table4 = data4.add_worksheet()

    data5 = xlsxwriter.Workbook(filename5)    # Table2處理後的樣本Table5
    table5 = data5.add_worksheet()

    writeoneline(table2,table3,0,0)
    writeoneline(table1,table4,0,0)
    writeoneline(table2,table5,0,0)
    nrows3=1
    nrows4=1
    nrows5=1

    for i in range(1,nrows1):
        if count1[i]  in count2:
            writeoneline(table1,table3,i,nrows3)                # Table1寫入共同使用者樣本Table3
            nrows3+=1
        else:
            writeoneline(table1,table4,i,nrows4)                # Table1寫入處理後的樣本Table4
            nrows4+=1
        if i%10000==0:
            done=i/(nrows1+nrows2)
            sys.stdout.write("\r[%s%s] %d%%" % ('█'*int(40*done),'  '*(40-int(40*done)),int(100*done)))
            sys.stdout.flush()
    data3.close()
    print("Write to Table3 Successful!")
    data4.close()
    print("Write to Table4 Successful!")

    done=nrows2/(nrows1+nrows2)
    sys.stdout.write("\r[%s%s] %d%%" % ('█'*int(40*done),'  '*(40-int(40*done)),int(100*done)))
    sys.stdout.flush()

    for i in range(1,nrows2):
        if count2[i] not in count1:
            writeoneline(table2,table5,i,nrows5)                # table2寫入處理後的樣本Table5
            nrows5+=1
        if i%10000==0:
            done=(i+nrows2)/(nrows1+nrows2)
            sys.stdout.write("\r[%s%s] %d%%" % ('█'*int(40*done),'  '*(40-int(40*done)),int(100*done)))
            sys.stdout.flush()
    data5.close()
    print("Write to Table5 Successful!")