본문 바로가기

Python/Let's Get IT 파이썬 프로그래밍

[Let's Get IT 파이썬 프로그래밍] 12. 프로그램 화면 구성 (tkinter)

728x90
반응형

  마지막 단원이다. 거듭 언급하지만, 필자가 작성한 코드는 대부분 'Let's Get IT 파이썬 프로그래밍'의 저자, 안지혜 선생님의 코드에 기반하고, 인용한 것임을 밝힌다. 원 코드를 확인하고 싶다면, 교재에 적힌 Github를 찾아가보길 바란다.

 

  사실 빅데이터를 활용한 머신러닝에 관심이 있어 코딩을 시작했는데 마지막 단원인 프로그램을 통한 시각화까지는 굳이 할 필요가 없었다고 생각한다. 그래도 배워두어서 나쁠 건 없고, 그동안에 배운 내용을 정리한다는 측면에서 의의가 있을 것이다.  


 

1. 환경 설정

구글 클라우드를 서버로 하는 구글 코랩으로는 진행할 수 없다. tkinter 패키지는 (로컬) 서버가 구성된 컴퓨터로만 진행이 가능하기 때문이다. 

 

새로 쓰는 라이브러리는 이 정도라고 본다.

from tkinter import *
from tkinter.ttk import * # 콤보박스용
from tkinter.messagebox import * #msgbox용

- "import tkinter"로 쓰면 클래스명 앞에 계속 tkinter를 넣어줘야 함.

- 동적변수 : 화면이 생성되고 나서 변하는 값을 저장하는 변수

2. 십구십구단

교재에서는 새로운 개념이 나올 때마다 구구단과 로그인 화면을 구현하는 코드에 적용시켜서 설명하는데 필자는 그냥 이전 단계의 코드를 주석 처리하며 계속 작성한 결과를 공유한다.

from tkinter import *
from tkinter.ttk import * # 콤보박스용
from tkinter.messagebox import * #msgbox용
import random as ran

# ******** 프로그램 기능 ************
n1, n2 = 0, 0

# 19X19 문제 함수
def test():
    
    # 외부에 정의한 전역 변수를 global을 사용해 값을 변경 가능하도록 함.
    global n1, n2
    n1, n2 = ran.randint(2, 19), ran.randint(1, 19)
    q.set(str(n1) + ' x '+ str(n2) + " = ? ")
    
    
    # 콤보박스
    # 선택지 초기화
    new = [ran.randint(2, 361) for i in range(10)]
        
    # 랜덤 위치에 정답 추가
    idx = ran.randint(0, 10)
    new.insert(idx, n1*n2)
    
    # 콤보박스에 리스트 연결
    a_cb['values'] = new
    
    
def ans():
    if int(a_cb.get()) == n1 * n2:
        showinfo(title = 'Evaluation', message = 'Go to Next Level')
    else :
        showerror(title = 'Evaluation', message = 'Review this Course')
    
    
# def ans():
#     a.set(n1 * n2)


# *********프로그램 화면 *************

# 창 생성
root = Tk()
root.title('19X19')


# 동적변수 (정수 : IntVar, 실수 : DoubleVar)
q = StringVar()
q_lbl = Label(root, textvariable = q, width = 20, anchor = 'center')
q_lbl.grid(row = 0, column = 0)

# 버튼
q_btn = Button(root, text = 'New Test', width = 8,
              command = test)
q_btn.grid(row = 0, column = 1)


# 콤보박스
a_list = []
a_cb = Combobox(root, values = a_list, width = 30)
a_cb.grid(row = 1, column = 0)


# 버튼
a_btn = Button(root, text = 'Answer', width = 8,
              command = ans
              )
a_btn.grid(row = 1, column = 1)

# # 동적변수 (정수 : IntVar, 실수 : DoubleVar)
# a = StringVar()
# a_lbl = Label(root, textvariable = a, width = 20)
# a_lbl.grid(row = 1, column = 0)

root.mainloop()

 

3. 로그인 화면

# ******** 프로그램 기능 ************
def login():
    if id_ent.get() == 'Will' and pw_ent.get() == 'Fatih':
        result.set('접속 중')
    else:
        result.set('접속 실패. \nID와 Password를 재확인하시오.')
    

# *********프로그램 화면 *************

# 창 생성
root = Tk()
root.title('Access')
root.geometry('500x300')


# 프레임
frame1 = Frame(root, relief = 'groove', borderwidth = 4)
frame1.grid(row = 0,column = 0)
frame2 = Frame(root, relief = 'groove', borderwidth = 4)
frame2.grid(row = 1, column = 0)


id_lbl = Label(frame1, text = "ID", width = 8)
id_lbl.grid(row = 0, column = 0)

# 엔트리
id_ent = Entry(frame1, width = 15)
id_ent.grid(row = 0, column = 1)


pw_lbl = Label(frame2, text = "Password", width = 8)
pw_lbl.grid(row = 0, column = 0)

# 엔트리
pw_ent = Entry(frame2, width = 15)
pw_ent.grid(row = 0, column = 1)


# 버튼
btn = Button(root, text = 'Sign In', width = 8,
              command = login)
btn.grid(row = 0, column = 2, rowspan = 2)


# 동적변수
result = StringVar()
re_lbl = Label(root, textvariable = result, width = 30)
re_lbl.grid(row = 2, column = 0, columnspan = 3)

root.mainloop()

4. 프로젝트 화면

마지막 코드라 그런지 정말정말 길~다.

from tkinter import *
from openpyxl import load_workbook as lw
import csv
import matplotlib.pyplot as plt
import datetime as dt


# ********* 프로그램 기능 ***********
def load_file():
    # 생활인구 데이터
    f = open("./data/LOCAL_PEOPLE_DONG_202206.csv", 
            encoding = 'utf8')
    data = csv.reader(f)
    next(data) # 컬럼명 제거
    data = list(data)

    # 행정동 코드 데이터
    code = lw("./data/행정동코드_매핑정보_20200325.xlsx", 
                            data_only = True) # 수식 제외 셀값만 가져오기

    # 행정동코드 시트의 데이터 가져오기
    code = code['행정동코드']

    # 행과 열의 데이터 출력 
    all_cell = []

    # code 데이터를 행 단위로 가져온다.
    for r in code.rows:
        r_value = [] # 빈 셀을 만든다

        # 행 단위로 가져온 데이터에서 셀값을 추출해 리스트에 담는다.
        for cell in r:
            r_value.append(cell.value)

        # 위의 과정을 반복하며 모든 데이터를 가져온다.
        all_cell.append(r_value)

    # 컬럼을 제외한다.
    code = all_cell[2:]
    
    # 위경도 데이터
    f2 = open("./data/서울시 행정동별 전력 사용량 2008년 위치정보 (좌표계_ WGS1984).csv", 
            encoding = 'cp949')
    where = csv.reader(f2)
    next(where) # 컬럼명 제거
    where = list(where)
    
    # 행정동 데이터 자료형 변환
    for row in data:
        for i in range(32):
            if i == 0 :
                row[i] = str(row[i])
            elif i <= 2 :
                row[i] = int(row[i])
            else :
                row[i] = float(row[i])

    # 코드 데이터 자료형 변환
    for row in code:
        row[1] = int(row[1])
        
    # 위경도 데이터 자료형 변환
    for row in where:
        row[2], row[-2], row[-1] = int(row[2]), float(row[-2]), float(row[-1])  
        
    return data, code, where


# 행정동 코드 찾기 함수
def find_dong1():
    
    global dong1, dong_code1
    dong1 = dong_ent1.get()
    
    for row in code:
        if row[-1] == dong1:
            dong_code1 = row[1]

    info.set(dong1 + "(" + str(dong_code1) + ")" + "을 살펴봅니다!")

    
# 비교 행정동 코드 찾기 함수
def find_dong2():
    
    global dong2, dong_code2
    dong2 = dong_ent2.get()
    
    for row in code:
        if row[-1] == dong2:
            dong_code2 = row[1]

    info.set(dong2 + "(" + str(dong_code2) + ")" + "을 살펴봅니다!")

# 그래프 그리기
def linegraph(pop_list, label_list, title):
    plt.figure(figsize = (10,8))
    plt.rc('font', family = "Malgun Gothic")
    plt.title(title, pad = 15)

    for i in range(len(pop_list)):
        plt.plot(range(24), pop_list[i], label = label_list[i])
        
    plt.xticks(range(24), range(24))
    plt.legend(loc = "best")
    plt.savefig('./data/graph.png')
    plt.show() 
    
    # 이미지 레이블로 표시
    img = PhotoImage(file = './data/graph.png')
    
    # configure : 레이블에 이미지 표시
    graph_lbl.configure(image = img)
    
    # 이미지를 레이블에 표시
    graph_lbl.image = img


# 시간대별 분석
def time_pop(make_line = 'y'):
    
    pop = [0 for i in range(24)]
    
    for row in data :
        if row[2] == dong_code1:
            pop[row[1]] += row[3]

    pop = [i/31 for i in pop]
    
    if make_line == 'n':
        return pop
    
    pop = [pop]
    label = ['평균 생활인구']
    title = dong1 + " 시간대별 평균 생활인구"
    linegraph(pop, label, title)    
    
    
# 주중/말 분석
def week_pop():
    
    weekday = [0 for i in range(24)]
    weekend = [0 for i in range(24)]

    for row in data:
        if row[2] == dong_code1:
            if dt.date(int(row[0][:4]), int(row[0][4:6]), int(row[0][6:])).weekday() < 5:
                weekday[row[1]] += row[3]
            else :
                weekend[row[1]] += row[3]

    # 2021년 1월의 주중/주말 일수 구하기
    day_num, end_num = 0,0

    for i in range(1,32):
        if dt.date(2021,1,i).weekday() < 5:
            day_num +=1
        else:
            end_num +=1

    weekday = [w/day_num for w in weekday]
    weekend = [w/end_num for w in weekend]
    
    tar_data = [weekday, weekend]
    label = ['주중', '주말']
    title = dong1 + " 주중/말 평균 생활인구"
    linegraph(tar_data, label, title)

# 성별 분석
def sex_pop():
    
    man = [0 for i in range(24)]
    woman = [0 for i in range(24)]

    for row in data:
        if row[2] == dong_code1:
            man[row[1]] += sum(row[4:18])
            woman[row[1]] += sum(row[18:32])

    man = [m/31 for m in man]
    woman = [w/31 for w in woman]
    
    tar_data = [man, woman]
    label = ['남', '여']
    title = dong1 + " 성별 평균 생활인구"
    linegraph(tar_data, label, title)
    
    
# 비교 분석
def diff_pop():
    
    # 두 지역의 평균 생활인구 구하기
    pop_a = time_pop('n')
    pop_b = [0 for i in range(24)]
    for row in data :
        if row[2] == dong_code2:
            pop_b[row[1]] += row[3]

    pop_b = [i/31 for i in pop_b]
        
    
    # 꺾은선 그래프 그리기
    tar_data = [pop_a, pop_b]
    label = [dong1, dong2]
    title = dong1 + " & " + dong2 + " 시간대별 평균 생활인구"
    linegraph(tar_data, label, title)

    
# 변수 설정 (lambda를 써서 인자 전달 가능)
data, code, where = load_file()
dong1, dong_code1 = '',''
dong2, dong_code2 = '',''


# ********* 프로그램 화면 ************
root = Tk()
root.geometry('900x700')
root.title("서울시 생활인구 분석")


# ********* 프레임 ************
tab = Frame(root)
tab.grid(row = 0, column = 0)

graph = Frame(root)
graph.grid(row = 1, column = 0)


# ********* 탭 ***********
dong_ent1 = Entry(tab, width = 22)
dong_ent1.grid(row= 0, column = 0)

dong_btn1 = Button(tab, text = "분석할 행정동 선택", width = 22,
                  command = find_dong1)
dong_btn1.grid(row= 0, column = 1)

dong_ent2 = Entry(tab, width = 22)
dong_ent2.grid(row= 0, column = 2)

dong_btn2 = Button(tab, text = "비교할 행정동 선택", width = 22,
                  command = find_dong2)
dong_btn2.grid(row= 0, column = 3)

info = StringVar()
dong_lbl = Label(tab, textvariable = info, font = ("Malgun Gothic", 14))
dong_lbl.grid(row= 1, columnspan = 4)


# ********** 그래프 프레임 ***********
btn1 =Button(graph, text = "시간대별 분석", width = 22, 
            command = time_pop)
btn1.grid(row= 0, column = 0)

btn2 = Button(graph, text = "주중/주말 분석", width = 22,
             command = week_pop)
btn2.grid(row= 0, column = 1)

btn3 = Button(graph, text = "성별 분석", width = 22,
             command = sex_pop)
btn3.grid(row= 0, column = 2)

btn4 = Button(graph, text = "타행정동 비교", width = 22,
             command = diff_pop)
btn4.grid(row= 0, column = 3)

graph_lbl = Label(graph)
graph_lbl.grid(row = 1, column = 0, columnspan = 5)

root.mainloop()

 

 

'오징어게임', '응답하라1988'의 배경인 쌍문동의 평균 생활인구를 분석해봤다. 지금까지 살펴본 번화가와 달리, 쌍문동은 명백히 베드타운의 기능을 수행하는 행정동으로 볼 수 있다. 쌍문2동에서 22시 이후에 평균 생활인구가 감소하는 것은 왜일까? 야간 근로자가 출근을 하기 때문일까? 문득 궁금해진다. 


드디어 장장 수개월에 걸친 'Let's Get IT 파이썬 프로그래밍' 여정이 끝났다. 파이썬의 기초를 어느 정도 쌓았으니 이제 본격적으로 머신러닝을 공부해볼 것이다. 오늘은 이만 쉬자. 휴식의 중요성은 굳이 말할 필요는 없겠지. 지식을 찾는 이에게 한 줄기 빛이 됐길 바라며 이 카테고리를 마무리한다. 

 

* P.S 필자의 Github 주소 : https://github.com/FATIH-BIGDATA/Gilbut_PythonProgramming

728x90
반응형