파이썬 10만번의 DataFrame 생성

조회수 742회

안녕하세요. 다름아니라 PyQt5로 여러 개의 값을 입력받아서 그것을 결국에는 화면에 그래프로 출력하고, 입력받은 데이터를 계산하고 필터링해서 엑셀로 저장할 수도 있는 프로그램을 만들고 싶은 학생입니다. 현재 어느정도 구글링해가면서 코드를 작성해봤는데, 1만번까지는 (빠르지는 않지만) 1분 안에는 실행되는 것 같은데 10만번을 돌리면 프로그램이 아예 멈춰버리네요.

조금 더 효율적으로 반복을 수행할 수 있는 방법이 있을까해서 글을 올립니다. 아래 코드 중 # for Loop 실행 메소드에서 self.data를 수행하는 데 오래걸리는 것 같습니다.

결국 제가 하려는 것은 어떤 금액을 입력하고 그 금액의 +범위, -범위를 지정해서 랜덤으로 추출한 후 해당 범위로부터의 오차들의 표준편차를 구하려 합니다.

import sys
import random
from PyQt5.Qt import *
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
import matplotlib.pyplot as pp

class BP3_UI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setGeometry(100, 250, 500, 800)
        self.resize(985, 760)
        self.setWindowTitle("BP3")
        self.UI()

        '''
        아이콘 추가 시 
        self.setWindowIcon(QIcon("icon.png"))
        '''

    def UI(self):
        # 라벨 폰트
        font = QtGui.QFont()
        font.setFamily("굴림")
        font.setPointSize(10)

        '''
        # 메뉴바 생성
        self.statusBar()
        menubar = self.menuBar()
        menubar.setNativeMenuBar(False)
        menu_1 = menubar.addMenu('&정보')
        '''

        # 라벨 생성
        self.label_cnt_0 = QLabel("[1] 반복 횟수 : ", self)
        self.label_cnt_0.setGeometry(20, 40, 121, 16)
        self.label_cnt_0.setFont(font)

        self.label_input_0 = QLabel("[2] 예비 가격 : ", self)
        self.label_input_0.setGeometry(QtCore.QRect(20, 70, 121, 16))
        self.label_input_0.setFont(font)

        self.label_input_1 = QLabel("[3] +범위 예가율 : ", self)
        self.label_input_1.setGeometry(QtCore.QRect(20, 130, 121, 16))
        self.label_input_1.setFont(font)

        self.label_cnt_1 = QLabel("[4] +범위 추출 수량 : ", self)
        self.label_cnt_1.setGeometry(20, 160, 131, 16)
        self.label_cnt_1.setFont(font)

        self.label_input_2 = QLabel("[5] -범위 예가율 : ", self)
        self.label_input_2.setGeometry(QtCore.QRect(20, 220, 121, 16))
        self.label_input_2.setFont(font)

        self.label_cnt_2 = QLabel("[6] -범위 추출 수량 : ", self)
        self.label_cnt_2.setGeometry(20, 250, 131, 16)
        self.label_cnt_2.setFont(font)

        self.label_cnt_3 = QLabel("[7] 최종 출력 수량 : ", self)
        self.label_cnt_3.setGeometry(20, 310, 121, 16)
        self.label_cnt_3.setFont(font)

        self.label_unit_1 = QLabel("번", self)
        self.label_unit_1.setGeometry(260, 40, 21, 16)
        self.label_unit_1.setFont(font)

        self.label_unit_2 = QLabel("원", self)
        self.label_unit_2.setGeometry(260, 70, 21, 16)
        self.label_unit_2.setFont(font)

        self.label_unit_3 = QLabel("%", self)
        self.label_unit_3.setGeometry(260, 130, 21, 16)
        self.label_unit_3.setFont(font)

        self.label_unit_4 = QLabel("개", self)
        self.label_unit_4.setGeometry(260, 160, 21, 16)
        self.label_unit_4.setFont(font)

        self.label_unit_5 = QLabel("%", self)
        self.label_unit_5.setGeometry(260, 220, 21, 16)
        self.label_unit_5.setFont(font)

        self.label_unit_6 = QLabel("개", self)
        self.label_unit_6.setGeometry(260, 250, 21, 16)
        self.label_unit_6.setFont(font)

        self.label_unit_7 = QLabel("개", self)
        self.label_unit_7.setGeometry(260, 310, 21, 16)
        self.label_unit_7.setFont(font)

        # 라인에딧(텍스트 박스) 생성
        self.lineedit_cnt_0 = QLineEdit(self)
        self.lineedit_cnt_0.setGeometry(120, 40, 131, 20)
        self.lineedit_cnt_0.setFont(font)
        self.lineedit_cnt_0.setPlaceholderText("횟수 입력")
        self.lineedit_cnt_0.setDragEnabled(True)

        self.lineedit_input_0 = QLineEdit(self)
        self.lineedit_input_0.setGeometry(120, 70, 131, 20)
        self.lineedit_input_0.setFont(font)
        self.lineedit_input_0.setPlaceholderText("예상가격 입력")
        self.lineedit_input_0.setDragEnabled(True)

        self.lineedit_input_1 = QLineEdit(self)
        self.lineedit_input_1.setGeometry(140, 130, 111, 20)
        self.lineedit_input_1.setFont(font)
        self.lineedit_input_1.setPlaceholderText("예가율 입력")
        self.lineedit_input_1.setDragEnabled(True)

        self.lineedit_cnt_1 = QLineEdit(self)
        self.lineedit_cnt_1.setGeometry(160, 160, 91, 20)
        self.lineedit_cnt_1.setFont(font)
        self.lineedit_cnt_1.setPlaceholderText("수량 입력")
        self.lineedit_cnt_1.setDragEnabled(True)

        self.lineedit_input_2 = QLineEdit(self)
        self.lineedit_input_2.setGeometry(140, 220, 111, 20)
        self.lineedit_input_2.setFont(font)
        self.lineedit_input_2.setPlaceholderText("예가율 입력")
        self.lineedit_input_2.setDragEnabled(True)

        self.lineedit_cnt_2 = QLineEdit(self)
        self.lineedit_cnt_2.setGeometry(160, 250, 91, 20)
        self.lineedit_cnt_2.setFont(font)
        self.lineedit_cnt_2.setPlaceholderText("수량 입력")
        self.lineedit_cnt_2.setDragEnabled(True)

        self.lineedit_cnt_3 = QLineEdit(self)
        self.lineedit_cnt_3.setGeometry(150, 310, 101, 20)
        self.lineedit_cnt_3.setFont(font)
        self.lineedit_cnt_3.setPlaceholderText("수량 입력")
        self.lineedit_cnt_3.setDragEnabled(True)

        # 구분선 생성
        self.line_0_1 = QFrame(self)
        self.line_0_1.setGeometry(20, 10, 80, 16)
        self.line_0_1.setFrameShape(QFrame.HLine)
        self.line_0_1.setFrameShadow(QFrame.Sunken)

        self.label_div_0 = QLabel("[기초 입력사항]", self)
        self.label_div_0.setGeometry(90, 10, 121, 16)
        self.label_div_0.setFont(font)
        self.label_div_0.setAlignment(QtCore.Qt.AlignCenter)

        self.line_0_2 = QFrame(self)
        self.line_0_2.setGeometry(200, 10, 81, 16)
        self.line_0_2.setFrameShape(QFrame.HLine)
        self.line_0_2.setFrameShadow(QFrame.Sunken)

        self.line_1_1 = QFrame(self)
        self.line_1_1.setGeometry(20, 100, 77, 16)
        self.line_1_1.setFrameShape(QFrame.HLine)
        self.line_1_1.setFrameShadow(QFrame.Sunken)

        self.label_div_1 = QLabel("[+범위 입력사항]", self)
        self.label_div_1.setGeometry(90, 100, 121, 16)
        self.label_div_1.setFont(font)
        self.label_div_1.setAlignment(QtCore.Qt.AlignCenter)

        self.line_1_2 = QFrame(self)
        self.line_1_2.setGeometry(203, 100, 81, 16)
        self.line_1_2.setFrameShape(QFrame.HLine)
        self.line_1_2.setFrameShadow(QFrame.Sunken)

        self.line_2_1 = QFrame(self)
        self.line_2_1.setGeometry(20, 190, 77, 16)
        self.line_2_1.setFrameShape(QFrame.HLine)
        self.line_2_1.setFrameShadow(QFrame.Sunken)

        self.label_div_2 = QLabel("[-범위 입력사항]", self)
        self.label_div_2.setGeometry(90, 190, 121, 16)
        self.label_div_2.setFont(font)
        self.label_div_2.setAlignment(QtCore.Qt.AlignCenter)

        self.line_2_2 = QFrame(self)
        self.line_2_2.setGeometry(203, 190, 81, 16)
        self.line_2_2.setFrameShape(QFrame.HLine)
        self.line_2_2.setFrameShadow(QFrame.Sunken)

        self.line_3_1 = QFrame(self)
        self.line_3_1.setGeometry(20, 280, 80, 20)
        self.line_3_1.setFrameShape(QFrame.HLine)
        self.line_3_1.setFrameShadow(QFrame.Sunken)

        self.label_div_3 = QLabel("[최종 입력사항]", self)
        self.label_div_3.setGeometry(90, 280, 121, 16)
        self.label_div_3.setFont(font)
        self.label_div_3.setAlignment(QtCore.Qt.AlignCenter)

        self.line_3_2 = QFrame(self)
        self.line_3_2.setGeometry(200, 280, 81, 20)
        self.line_3_2.setFrameShape(QFrame.HLine)
        self.line_3_2.setFrameShadow(QFrame.Sunken)

        # 버튼 생성
        self.btn_1 = QtWidgets.QPushButton("실행", self)
        self.btn_1.setGeometry(20, 370, 81, 31)
        self.btn_1.setFont(font)
        self.btn_1.clicked.connect(self.btn_1_clicked)  # 실행 버튼 이벤트 메소드 연결

        self.btn_2 = QPushButton("리셋", self)
        self.btn_2.setGeometry(110, 370, 81, 31)
        self.btn_2.setFont(font)
        self.btn_2.clicked.connect(self.btn_2_clicked)  # 리셋 버튼 이벤트 메소드 연결

        self.btn_3 = QPushButton("저장", self)
        self.btn_3.setGeometry(200, 370, 81, 31)
        self.btn_3.setFont(font)
        self.btn_3.clicked.connect(self.btn_3_clicked)  # 저장 버튼 이벤트 메소드 연결

    # 종료버튼 누를 시 대화창 생성 메소드
    def closeEvent(self, QCloseEvent):
        question = QMessageBox.question(self, "종료 확인", "종료를 하시겠습니까?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        QCloseEvent.accept() if question == QMessageBox.Yes else QCloseEvent.ignore()

    # 테이블 생성 메소드
    def make_Table(self):
        self.data = pd.DataFrame(columns=['cnt', 'previous', 'rate', 'rated', 'rated_beta', 'condition', 'condition_beta'])
        self.InputValue()
        self.cnt_0 = int(self.lineedit_cnt_0.text())
        self.cnt_1 = int(self.lineedit_cnt_1.text())
        self.cnt_2 = int(self.lineedit_cnt_2.text())
        self.cnt_3 = int(self.lineedit_cnt_3.text())
        self.input_0 = int(self.lineedit_input_0.text())
        self.range_1 = int(self.input_0 + self.input_0 * self.input_1 / 100)
        self.range_2 = int(self.input_0 - self.input_0 * self.input_2 / 100)
        self.Start()
        self.List()
        self.DeleteNull()
        self.Category()

    # LineEdit 자료형 자동 변환 메소드
    def InputValue(self):
        val_int = QIntValidator()
        val_double = QDoubleValidator()
        self.input_1 = float(self.lineedit_input_1.text()) if '.' in self.lineedit_input_1.text() else int(self.lineedit_input_1.text())
        self.input_2 = float(self.lineedit_input_2.text()) if '.' in self.lineedit_input_2.text() else int(self.lineedit_input_2.text())

    # for Loop 실행 메소드
    def Start(self):
        for i in range(0, self.cnt_0) :
            self.list_sum = random.sample(range(self.input_0, self.range_1), self.cnt_1) + random.sample(range(self.range_2, self.input_0), self.cnt_2)
            self.list_3 = random.sample(self.list_sum, self.cnt_3)
            self.avg = sum(self.list_3) / self.cnt_3
            self.d_rate = self.avg / self.input_0
            self.condition = float((self.input_1 * 100 - 5 * i) / 10000)
            self.data = self.data.append(pd.DataFrame([[i+1, self.avg, self.d_rate, self.d_rate-1, int((self.d_rate-1)*10000), "" if self.condition < 0 and round(-self.input_1/100, 4) > round(self.condition, 4) else self.condition, "" if self.condition < 0 and round(-self.input_1/100, 4) > round(self.condition, 4) else int(self.condition*10000)]], columns=['cnt', 'previous', 'rate', 'rated', 'rated_beta', 'condition', 'condition_beta']), ignore_index=True)

    # 리스트로 변환
    def List(self):
        self.list_rated = self.data.iloc[:, 4].tolist()
        self.condition_tolist = self.data.iloc[:, 6].tolist()

    # 변환한 리스트 안에 빈 데이터('') 제거
    def DeleteNull(self):
        self.list_condition = [x for x in self.condition_tolist if x]
        self.list_condition.append(0)
        self.list_condition.sort()

    # 카테고리화
    def Category(self):
        self.category = pd.cut(self.list_rated, self.list_condition)
        self.series = self.category.value_counts()
        self.value = self.category.value_counts().to_frame() # Category → DataFrame
        self.value = self.value.reset_index() # 새로운 index로 변경
        self.value.columns = ["range", "count"]
        self.data_sum = pd.merge(self.data, self.value, how="outer", left_index=True, right_index=True) # 데이터 결합

    # 그래프 생성 메소드
    def make_Graph(self):
        self.graph = self.value.plot.barh(x='range', y='count')
        pp.show()

    # 버튼 '실행' 클릭 이벤트 메소드 생성
    def btn_1_clicked(self):
        self.make_Table()
        self.make_Graph()

    # 버튼 '리셋' 클릭 이벤트 메소드 생성
    def btn_2_clicked(self):
        self.lineedit_cnt_0.setText("")
        self.lineedit_cnt_1.setText("")
        self.lineedit_cnt_2.setText("")
        self.lineedit_cnt_3.setText("")
        self.lineedit_input_0.setText("")
        self.lineedit_input_1.setText("")
        self.lineedit_input_2.setText("")

    # 버튼 '저장' 클릭 이벤트 메소드 생성
    def btn_3_clicked(self):
        print("저장")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = BP3_UI()
    window.show()
    app.exec_()
  • dataframe은 잘 모릅니다만 그냥 참견을 하자면... 새 값을 직전 결과값에 추가해서 결과를 얻고 거기에 또 새 값을 더해서 결과를 얻고 하는 걸 무한 반복하는 방식은 어떤가요? 예전에 몬테카를로 근사 관련 답변달때 해보니 10만번 연산을 돌려도 결과가 잘 떨어지더군요. 엽토군 2019.8.30 15:48

1 답변

  • https://stackoverflow.com/questions/31690076/creating-large-pandas-dataframes-preallocation-vs-append-vs-concat 도 참고하시고요.

        def make_Table(self):
            self.data = [] #pd.DataFrame(columns=['cnt', 'previous', 'rate', 'rated', 'rated_beta', 'condition', 'condition_beta'])
            # 생략...
    
        def Start(self):
            for i in range(0, self.cnt_0) :
                self.list_sum = random.sample(range(self.input_0, self.range_1), self.cnt_1) + random.sample(range(self.range_2, self.input_0), self.cnt_2)
                self.list_3 = random.sample(self.list_sum, self.cnt_3)
                self.avg = sum(self.list_3) / self.cnt_3
                self.d_rate = self.avg / self.input_0
                self.condition = float((self.input_1 * 100 - 5 * i) / 10000)
                self.data.append((i+1, self.avg, self.d_rate, self.d_rate-1, int((self.d_rate-1)*10000), "" if self.condition < 0 and round(-self.input_1/100, 4) > round(self.condition, 4) else self.condition, "" if self.condition < 0 and round(-self.input_1/100, 4) > round(self.condition, 4) else int(self.condition*10000)))
            self.data = pd.DataFrame(self.data, columns=['cnt', 'previous', 'rate', 'rated', 'rated_beta', 'condition', 'condition_beta'])
    

    이렇게만 바꿔도 매 데이터 라인마다 데이터프레임을 생성하고, append 하는 오버헤드가 줄어들면서 훨씬 빨라지네요.

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)