본문 바로가기
공부/python

[Pytesseract를 사용한 메이플스토리 길드 스코어 분석] 저장된 엑셀 파일 분석하기

by 고기 2022. 3. 30.

이전 글에서 이미지를 문자로 변환한 것과 스크래핑을 통해 얻어온 데이터들을 취합해서 엑셀 파일로 저장하는 방법에 대해 알아보았다. 이번 글에서는 이렇게 저장된 파일을 불러와서 분석하고 결과를 다시 저장하는 방법에 대해 알아본다.

 

전에 작성했던 것과 마찬가지로 저장된 파일을 분석하는 버튼을 생성한다.

library list
import tkinter as tk
import tkinter.filedialog as fd
from tkinter import messagebox
from tkinter import *

import openpyxl
from openpyxl.styles import Alignment, Font, Border, Side, PatternFill

import os
import pandas as pd
import numpy as np
 
class UI:
    def __init__(self):   
        variable & window & button setting
        
        '''
        파일 분석 버튼        
        '''
        self.label_analysis = tk.Label(self.main_window, text = "[4] 파일분석", font=font, bg="pink")
        self.label_analysis.place(x=250, y=210)        
        btn_analysis = tk.Button(self.main_window, text="Analysis", font=font, bg="pink", overrelief="solid", command=self.call_file_analysis, repeatdelay=1000)
        btn_analysis.place(x=250, y=250)        

    '''
    파일 분석
    '''
    def call_file_analysis(self):                
        self.main_window.destroy()

 

분석 버튼 아래에 기준 점수를 체크할 수 있도록 체크박스를 추가한다. 주간미션, 지하수로, 플래그 세 개의 체크박스 코드는 이름만 다를 뿐  완전히 동일한 기능이므로 설명할 때는 주간미션 체크박스 코드만 가지고 설명한다.

 

10~11라인에서 Checkbox에 대한 variable을 CheckVal로 지정했다. CheckVal 변수는 flag변수로 즉, checkbox가 체크되었는지 확인하기 위한 변수다. CheckVal 변수의 기본 값은 0이며, checkbox가 체크되면 1을 가진다. 그 아래 13~14라인에서 text는 점수를 입력할 수 있는 입력칸으로, 기본상태를 disabled로 설정해서 입력할 수 없도록 했다.

library list

class UI:
    def __init__(self):        
        variable & window & button setting
        
        '''
        체크박스
        '''
        self.missionCheckVal=IntVar()
        self.missionCheckbox=Checkbutton(self.main_window,text="주간미션",font=font2, variable=self.missionCheckVal, command = self.call_check_mission)
        self.missionCheckbox.place(x=250, y=300)
        self.missiontext = tk.Entry(self.main_window, width=5, textvariable=str)
        self.missiontext.configure(state='disabled')
        self.missiontext.place(x=330, y=300)
        
        self.main_window.mainloop()
        
    '''
    체크박스 체크되었는지 여부 검사하는 함수
    '''
    def call_check_mission(self):
        self.main_window.destroy()
        
    def function list

 

11라인에서 Checkbox에 대한 command 함수를 check_misson() 함수로 지정했다. check_mission() 함수에서는 14라인에서 설정했던 입력칸의 disabled 상태를 checkbox가 체크되었는지 아닌지에 따라 normal / disabled 상태로 설정하고, disabled상태일때, 입력칸의 모든 글자를 지운다. checkbox의 값은 CheckVal.get() 으로 받아올 수 있다.

library list

class UI:
    def __init__(self):        
        variable & window & button setting
        
        '''
        체크박스
        '''
        self.missionCheckVal=IntVar()
        self.missionCheckbox=Checkbutton(self.main_window,text="주간미션",font=font2, variable=self.missionCheckVal, command = self.call_check_mission)
        self.missionCheckbox.place(x=250, y=300)
        self.missiontext = tk.Entry(self.main_window, width=5, textvariable=str)
        self.missiontext.configure(state='disabled')
        self.missiontext.place(x=330, y=300)
        
        self.main_window.mainloop()
        
    '''
    체크박스 체크되었는지 여부 검사하는 함수
    '''
    def call_check_mission(self):
        if self.missionCheckVal.get() == 1:
            self.missiontext.configure(state='normal')
        if self.missionCheckVal.get() == 0:            
            self.missiontext.delete(0,END)
            self.missiontext.configure(state='disabled')
        
    def function list

 

checkbox가 체크되면 입력칸이 활성화되는것을 확인할 수 있다.

사진1

 

이제 분석 버튼의 command 함수인 file_analysis() 함수를 작성한다. 코드를 설명하기 전에 함수의 작동과정을 간단히 설명하면 이렇다.

 

먼저 분석할 파일이 있는지 검사한다. 여기서 분석할 파일이란 이전 글에서 저장했던 길드원 닉네임과 점수를 매치시킨 리스트와 부캐 정리 리스트를 말한다.

사진2

 

불러온 파일에서 각 점수들을 기준 점수와 비교한다음, 분석결과를 새 파일로 저장한다.

사진3

 

다시 코드로 돌아와서, 파일 분석을 위한 함수를 작성한다. 먼저 15~21라인은 tkinter의 messagebox를 사용해서 저장된 파일을 선택하는 코드다. 다음으로 26~50라인은 list1과 list2를 비교해서 점수를 입력하는 코드인데, 이건 정말 말로는 설명을 못하겠어서 사진4로 설명을 대체한다...

library list

class UI:
    def __init__(self):   
        variable & window & button setting         

    '''
    파일 분석
    '''
    def call_file_analysis(self):                
        
        '''
        엑셀 읽어오기
        '''        
        date = self.date        
        msg1 = messagebox.askokcancel("선택해", "분석할 파일이 있는 폴더를 선택하세요")
        if msg1:
            path = fd.askdirectory(initialdir = "./", title = "수정한 파일이 있는 폴더 선택")
            date = path[path.find("202"):]
            read_guild_list1 = pd.read_excel(date+'/길드원 리스트.xlsx').fillna(' ')
            read_guild_list2 = pd.read_excel(date+'/여기에 부캐들 정렬할것.xlsx').fillna(' ')

            '''
            unnamed 제거
            '''
            Unnamed = []
            for word in read_guild_list2:
                if word.find("Unnamed") == 0:
                    Unnamed.append(word)
            read_guild_list2.drop(Unnamed, axis=1, inplace=True)

            '''
            각 index 읽어오기
            '''
            naming = []
            for name in range(0, int(len(read_guild_list2.columns)/2)):
                naming.append(read_guild_list2.columns[name*2])

            '''
            점수입력
            '''        
            for data1 in range(0, len(read_guild_list1)):
                for data2 in range(0, len(read_guild_list2)):
                    for data3 in range(0, len(naming)):
                        if read_guild_list1['이름'][data1] == read_guild_list2[naming[data3]][data2]:
                            read_guild_list1['이름']=read_guild_list1['이름'].drop(data1).fillna(' ')
                            read_guild_list2[naming[data3]+'점수'][data2] = str(read_guild_list1['주간미션'][data1]), str(read_guild_list1['지하수로'][data1]), str(read_guild_list1['플래그'][data1])
                            break     

            loc = len(read_guild_list2)

 

그러니까 이런식으로 길드에 부캐 넣어둔 사람들 있으면 찾아주려고 만든 코드다.

사진4

 

이 부분은 너무 긴것도 있지만 사실 진짜 그냥 행 늘리고 삭제하고, 데이터 조건에 따라 처리만 해준거라서 딱히 설명할 게 없기는 한데, 영혼을 끌어모아 길게 설명을 해보겠다!

 

30~39라인은 기준으로 입력된 주간미션, 지하수로, 플래그 점수를 가져오는 과정이다.

41~47라인은 데이터를 입력하기 위해 행을 추가하는 과정이다.

60~66라인은 각 길드원들의 부캐를 포함한 점수가 합산된다. 예를들면, 사진4에서 기록해둔 부캐들을 포함한 길드원A의 점수를 합산한다고 생각하면 된다.

71~79라인은 합산된 점수를 입력하는 과정이다.

84~97라인은 합산된 점수가 기준에 미치는지 확인하는 과정이다. 기준에 미치지 못하면 길드스킬을 못쓴다는 표시를, 기준에 충족되면 아무런 표시를 하지 않는다.

99~106라인은 전체 길드원 점수에서 자신의 점수가 어느정도 기여하는지 %로 출력하는 과정이다.

111~129라인은 이렇게 만든 파일을 저장하는 과정이다.

 

휴 길었다..

library list
 
class UI:
    def __init__(self):   
        variable & window & button setting         

    '''
    파일 분석
    '''
    def call_file_analysis(self):                
        
        msg1 = messagebox.askokcancel("선택해", "분석할 파일이 있는 폴더를 선택하세요")
        if msg1:
            점수입력

            '''
            주간미션, 지하수로, 플래그 점수 저장
            '''
            score = []
            if self.missionCheckVal.get() == 1:
                score.append(int(self.missiontext.get()))
                score.append(int(loc))
            if self.underCheckVar.get() == 1:        
                score.append(int(self.undertext.get()))
                score.append(int(loc+1))
            if self.flagCheckVar.get() == 1:         
                score.append(int(self.flagtext.get()))
                score.append(int(loc+2))
            
            data4 = {' ' : ' '}
            read_guild_list2 = read_guild_list2.append(data4, ignore_index=True).fillna(' ') # 주간미션용
            read_guild_list2 = read_guild_list2.append(data4, ignore_index=True).fillna(' ') # 지하수로용
            read_guild_list2 = read_guild_list2.append(data4, ignore_index=True).fillna(' ') # 플래그용
            read_guild_list2 = read_guild_list2.append(data4, ignore_index=True).fillna(' ') # 노블여부용
            read_guild_list2 = read_guild_list2.append(data4, ignore_index=True).fillna(' ') # 노블여부용
            read_guild_list2 = read_guild_list2.append(data4, ignore_index=True).fillna(' ') # 빈칸용  

            fm_sum = 0
            fu_sum = 0
            ff_sum = 0

            for data1 in range(0, len(naming)):
                m_sum = 0
                u_sum = 0
                f_sum = 0

                for data2 in range(0, len(read_guild_list2)):
                    try:
                        m_sum = m_sum + int(read_guild_list2[naming[data1]+'점수'][data2][0])
                        u_sum = u_sum + int(read_guild_list2[naming[data1]+'점수'][data2][1])
                        f_sum = f_sum + int(read_guild_list2[naming[data1]+'점수'][data2][2])

                        fm_sum = fm_sum + int(read_guild_list2[naming[data1]+'점수'][data2][0])
                        fu_sum = fu_sum + int(read_guild_list2[naming[data1]+'점수'][data2][1])
                        ff_sum = ff_sum + int(read_guild_list2[naming[data1]+'점수'][data2][2])
                        
                    except ValueError:                        
                        pass

                read_guild_list2[naming[data1]].loc[loc]=naming[data1]+'주간미션'
                read_guild_list2[naming[data1]+'점수'].loc[loc]=int(m_sum)

                read_guild_list2[naming[data1]].loc[loc+1]=naming[data1]+'지하수로'
                read_guild_list2[naming[data1]+'점수'].loc[loc+1]=int(u_sum)
                
                read_guild_list2[naming[data1]].loc[loc+2]=naming[data1]+'플래그'
                read_guild_list2[naming[data1]+'점수'].loc[loc+2]=int(f_sum)
                read_guild_list2[naming[data1]].loc[loc+3]=naming[data1]+'노블'

                '''
                주간미션, 지하수로, 플래그 점수 분석
                '''
                if len(score) == 6:
                    if (((read_guild_list2[naming[data1]+'점수'][score[1]] >= score[0]) and
                        (read_guild_list2[naming[data1]+'점수'][score[3]] >= score[2]) and 
                        (read_guild_list2[naming[data1]+'점수'][score[5]]>= score[4])) != 1) :
                        read_guild_list2[naming[data1]].loc[loc+4]='너 못써~'

                if len(score) == 4:
                    if (((read_guild_list2[naming[data1]+'점수'][score[1]] >= score[0]) and
                        (read_guild_list2[naming[data1]+'점수'][score[3]] >= score[2])) != 1):
                        read_guild_list2[naming[data1]].loc[loc+4]='너 못써~'

                if len(score) == 2:
                    if (((read_guild_list2[naming[data1]+'점수'][score[1]] >= score[0])) != 1):
                        read_guild_list2[naming[data1]].loc[loc+4]='너 못써~'            

            for data1 in range(0, len(naming)):    
                m = (str("%.f%%" % (int(read_guild_list2[naming[data1]+'점수'][loc]) / int(fm_sum) * 100.0)))
                u = (str("%.f%%" % (int(read_guild_list2[naming[data1]+'점수'][loc+1]) / int(fu_sum) * 100.0)))
                f = (str("%.f%%" % (int(read_guild_list2[naming[data1]+'점수'][loc+2]) / int(ff_sum) * 100.0)))

                read_guild_list2[naming[data1]+'점수'].loc[loc] = str(read_guild_list2[naming[data1]+'점수'].loc[loc]) + "점 -> " + str(m)
                read_guild_list2[naming[data1]+'점수'].loc[loc+1] = str(read_guild_list2[naming[data1]+'점수'].loc[loc+1]) + "점 -> " + str(u)
                read_guild_list2[naming[data1]+'점수'].loc[loc+2] = str(read_guild_list2[naming[data1]+'점수'].loc[loc+2]) + "점 -> " + str(f)

            '''
            파일 저장
            '''
            new_base_dir3 = './'
            new_file_name3 = '분석'+date+'.xlsx'
            new_file_dir3 = os.path.join(new_base_dir3, new_file_name3)
            l = []

            for name in read_guild_list2.T.index.values:
                l.append(name)

            col1=read_guild_list2.T.columns[-6:-1].to_list()
            col2=read_guild_list2.T.columns[-1:].to_list()        
            col3=read_guild_list2.T.columns[:-6].to_list()
            new_col=col1+col2+col3

            read_guild_list2.T[new_col].to_excel(new_file_dir3,
                                                 na_rep='NaN',
                                                 header=False,
                                                 index=False,
                                                 startrow=0,
                                                 startcol=0)

 

이 부분은 파이썬에서 엑셀 파일을 조작하는 코드인데 그래프도 그릴 수 있고 다양한 기능이 있다. 나는 코드를 더 추가하자니 너무 복잡해져서 셀 너비 조절과 셀의 색을 변경해주는 정도만 사용했다.

 

21~22라인은 엑셀 시트를 읽어오는 과정이다.

24~25라인은 읽어온 시트에서 데이터가 존재하는 최대 행과 열의 개수를 읽어오는 과정이다.

30~32라인은 엑셀 시트의 각 열 너비를 조정하는 과정이다.

37~40라인은 읽어온 시트에서 row_count, col_count만큼 색칠하는 과정이다. 주의할점은 반복문을 돌릴 때 1부터 시작해야 한다는 점이다.

45~48라인은 중간 라인을 처리해주는 과정이다.

53~60라인은 길드스킬을 못쓰는 사람들을 찾아서 그 영역을 빨간색으로 색칠하는 과정이다.

62라인은 수정한 엑셀 시트를 업데이트하는 과정이다.

library list
 
class UI:
    def __init__(self):   
        variable & window & button setting         

    '''
    파일 분석
    '''
    def call_file_analysis(self):                
        
        msg1 = messagebox.askokcancel("선택해", "분석할 파일이 있는 폴더를 선택하세요")
        if msg1:
            점수입력
            주간미션, 지하수로, 플래그 점수 저장 및 분석
            파일 저장
            
            '''
            엑셀파일 분석
            '''
            wb = openpyxl.load_workbook(new_file_dir3)
            sheet = wb.active

            row_count = sheet.max_row
            col_count = sheet.max_column

            '''
            너비 설정
            '''
            width = 15
            for i in range(65, 91):
                sheet.column_dimensions[chr(i)].width=width

            '''
            전체 색칠하기
            '''
            for x in range(1, row_count):
                for y in range(1, col_count+1):
                    c = sheet.cell(row=x, column=y)
                    c.fill = PatternFill(start_color="ccffff", end_color="AAAA31", fill_type="solid")

            '''
            F행 따로 처리
            '''
            sheet.column_dimensions['F'].width=4
            for x in range(1, row_count):
                c = sheet.cell(row=x, column=6)
                c.fill = PatternFill(start_color="123456", end_color="123456", fill_type="solid")

            '''
            못쓰는 사람 처리
            '''
            for row in range(1, row_count):
                if sheet[f"E{row}"].value == "너 못써~" :        
                    for col in range(7, col_count):            
                        if sheet.cell(row=row, column=col).value == ' ':
                            break
                        else:
                            sheet.cell(row=row, column=col).fill = PatternFill(start_color="c12341", end_color="c23411", fill_type="solid")
                            sheet.cell(row=row+1, column=col).fill = PatternFill(start_color="c12341", end_color="c23411", fill_type="solid")

            wb.save(new_file_dir3)

 

분석 결과는 사진3과 같으니 참고하면 된다.

 

여기까지 엑셀 파일을 불러와서 엑셀 시트를 직접 수정하는 방법에 대해 알아보았다. 다음 글에서는 프로그램 사용 방법 등에 대해 알아본다.

댓글