코드품질향상을 위한 Python Skill 활용

어떤 일을 더 빠르고 효율적으로 또는 단순히 더 우아하게 끝내기 위한 빠른 스킬을 좋아하지 않는 사람이 어디 있겠습니까? 이번 포스팅에서는 생산성과 코드품질향상을 위한 Python Skill 5가지에 대해서 알아 보겠습니다.


모든 열의 이름 빨리 바꾸기

많은 상황에서 열 이름을 변경하는 것은 잘못된 선택일 수 있습니다. 그러나 목표가 단순히 데이터를 분석하거나 시각화를 생성하는 것이라면 공백, 악센트 또는 기호가 포함된 열 이름이 있는 DataFrame을 사용하는 것이 상당히 불편할 수 있습니다.
누군가가 Excel 열 이름 끝에 원치 않는 공백을 남긴 경우는 말할 것도 없고 표준화된 이름을 가진 열을 선택하는 것이 더 쉽습니다. 따라서 모든 열 이름을 snake_case 패턴으로 빠르게 변환할 수 있는 방법에 대해서 알아 보겠습니다.

import re
from unidecode import unidecode

def rename_columns(df):
"""모든 DataFrame의 열 이름을 snake_case 패턴으로 표준화
"""
for col in df.columns:
    newColName = re.sub('\W','',unidecode(col.lower().strip().replace(' ','_')))
    df.rename(columns = {col:newColName}, inplace = True)

# 코드 이해하기
# re.sub('\W','','string!@#') -- 영숫자가 아닌 문자 제거하여 'string' 반환
# unidecode('áçê') -- 악센트 및 특수 문자 제거하여 'áçê'' 반환
# 'STRING'.lower() -- 소문자로 변환하여 'string' 반환
# ' string '.strip() -- 문자열의 시작과 끝에서 공백 제거하여 '문자열' 반환
# 'my string'.replace(' ','_') -- ' '를 모두 '_'로 변경하여 'my_string' 반환


나만의 모듈 만들기

첫 번째 스킬에서 유용한 함수를 만들었고, 함수를 사용할 모든 스크립트에 복사하여 붙여넣기만 하면 됩니다. 될까요?
이런 방법보다는 다른 라이브러리에서 일반적으로 수행하는 것과 똑같이 함수를 가져오는 것은 어떨까요? 생각보다 매우 쉽고 다른 사람들과 작업을 공유하는 데 매우 유용할 수 있습니다.
함수가 포함된 Python 파일(.py)을 생성하고 해당 파일을 Python이 모듈 및 패키지를 확인하는 경로에 복사하기만 하면 됩니다. Python이 검사하는 경로를 찾으려면 선호하는 IDE에서 실행하시면 됩니다.

import sys
print(sys.path)
-----------------
['C:\\Users\\82108',
 'C:\\Users\\82108\\anaconda3\\python39.zip',
 'C:\\Users\\82108\\anaconda3\\DLLs',
 'C:\\Users\\82108\\anaconda3\\lib',
 'C:\\Users\\82108\\anaconda3',
 '',
 'C:\\Users\\82108\\anaconda3\\lib\\site-packages',
 'C:\\Users\\82108\\anaconda3\\lib\\site-packages\\locket-0.2.1-py3.9.egg',
 'C:\\Users\\82108\\anaconda3\\lib\\site-packages\\win32',
 'C:\\Users\\82108\\anaconda3\\lib\\site-packages\\win32\\lib',
 'C:\\Users\\82108\\anaconda3\\lib\\site-packages\\Pythonwin',
 'C:\\Users\\82108\\anaconda3\\lib\\site-packages\\IPython\\extensions',
 'C:\\Users\\82108\\.ipython']

이제 출력된 경로 중 하나에 파일을 복사하면(이름을 ‘my_first_module.py’라고 지정) 라이브러리처럼 가져올 수 있습니다.

# 함수 호출
my_first_module.rename_columns(any_df)


가짜 데이터 생성

테스트용이든 학습용이든 종종 가짜 변수를 만들어야 하는 상황이 있습니다. 예를 들어 단위 테스트를 작성하거나 새 라이브러리를 사용하려면 가짜 데이터가 필요할 수 있습니다.
물론 이러한 변수를 처음부터 가져오거나 만들 수 있지만 훨씬 더 쉽고 다재다능한 해결방안이 있습니다. 바로 Faker 라이브러리를 통해 다양한 유형과 특성의 임의(가짜) 데이터를 생성할 수 있습니다.

import pandas as pd
from faker import Faker

# Faker 객체 생성
fake = Faker()

# 가짜 데이터를 생성하기 위한 많은 옵션들
fake.name()
fake.text()
fake.address()
fake.email()
fake.date()
fake.country()
fake.phone_number()
fake.random_number(digits=5)

# 가짜 데이터 프레임 생성하기
fakerDf = pd.DataFrame({'date':[fake.date() for i in range(10)],
    'name':[fake.name() for i in range(10)],
    'email':[fake.email() for i in range(10)],
    'text':[fake.text() for i in range(10)]})
fakerDf
 datenameemailtext
01992-12-04Rebecca Schultzlisa23@example.netCold tax know. Tough action speech discussion …
11983-03-03Mark Lanerobertwilliams@example.comLow short property seem. Me dark enjoy only.
21975-04-04Mrs. Angela Rodriguez MDrachel43@example.orgLeave open meet rock deal become reduce respon…
31997-03-24Marie Williamsojohnston@example.netHuge issue stop candidate project responsibili…
42014-03-19Robin Gonzalesvictor18@example.netYoung necessary daughter training not over see…
51975-03-05Heather Chenhendrixsara@example.netNote than ability hand our. Theory simply real…
62012-02-27Nicholas Deleonthompsonstacie@example.netMany song audience sit unit institution south….
72000-06-27Janet Gibsonthomasthomas@example.comDemocrat raise north glass. Little shake fish …
82011-03-25Aaron Rogerssonya10@example.orgWhole no simple. Care official church occur bo…
91989-03-09Jamie Spencerhoffmanbelinda@example.orgMission might including build. Mr final popula…

몇 가지 예만 보여드렸지만, 훨씬 더 많은 가짜 데이터를 생성할 수 있습니다.


프로처럼 f-string 사용하기

Python을 공부하거나 작업하는 경우 이미 f-string을 사용하고 있을 것입니다. 매우 간단한 개념이죠. 기본적으로 문자열 앞에 문자 “f”를 사용하면 {} 내부의 텍스트에 변수를 삽입할 수 있습니다. 아래 예시를 보시죠.

age = 29
print(f'My age is {age}')
-----------------------------
My age is 29

f-string은 굉장합니다. 예를 들어 사용자 인터페이스를 개선하거나 더 나은 사용자 지정 로그를 생성할 수 있는 일련의 서식 옵션을 사용하여 훨씬 더 우아하고 전문적으로 만들 수 있습니다.

# f-string의 포맷 예시

number = 12.343562135
percentage_number = 0.33
high_number = 12349000212452

print(f'Printing without formatting: {number}'
    f'\nPrinting the var name: {number = }'
    f'\nShowing two decimal places: {number:.2f}'
    f'\nChanging characters wide: {number:30}'
    f'\nForces center alignment: {number:^30}'
    f'\nFill white spaces: {number:=^30}'
    f'\nPrinting percentages: {percentage_number:.2%}'
    f'\nShowing two decimal places with comma: {high_number:,.2f}'
    f'\nPrinting two decimal places with scientific notation: {high_number:.2e}'
    )
------------------------------------------------------------------------------------
Printing without formatting: 12.343562135
Printing the var name: number = 12.343562135
Showing two decimal places: 12.34
Changing characters wide:                   12.343562135
Forces center alignment:          12.343562135         
Fill white spaces: =========12.343562135=========
Printing percentages: 33.00%
Showing two decimal places with comma: 12,349,000,212,452.00
Printing two decimal places with scientific notation: 1.23e+13


로딩 바

긴 루프를 통과하는 코드를 실행해야 하고 아직 완료할 시간이 많은지 또는 모든 것이 예상대로 진행되고 있는지 궁금했던 적이 있지 않으신가요? 아래 코드를 활용하시면 됩니다.

import time

def any_func_with_loop():
"""로딩바를 이용한 함수 예시
"""

    listToLoop = [i for i in range(333)]
    tStart = time.perf_counter()
    percentageCounter = 0
    loopCounter = 0

    for item in listToLoop:

        # 코드 입력

        time.sleep(0.3)

        # 실행 진행 상황을 추적하는 코드
        loopCounter+=1
        percentage = round(100*(loopCounter)/len(listToLoop),0)
        if percentage%10 == 0:
            if percentageCounter != percentage: # 반올림된 %의 두 번 출력 방지
                tProv = time.perf_counter()
                    timeElapsed = tProv - tStart
        timeEstimated = (100*timeElapsed/percentage)-timeElapsed
        print(f'Status: {percentage:.0f}% Completed! -> '
            f'Elapsed time:{timeElapsed:.2f}s or {timeElapsed/60:.2f}min. | '
            f'Estimated time: {timeEstimated:.2f}s or {timeEstimated/60:.2f}min')
        percentageCounter = percentage # 출력된 마지막 백분율 저장

any_func_with_loop()
----------------------------------------------------------------------------------------------
Status: 10% Completed! -> Elapsed time:9.84s or 0.16min. | Estimated time: 88.57s or 1.48min
Status: 20% Completed! -> Elapsed time:20.00s or 0.33min. | Estimated time: 79.99s or 1.33min
Status: 30% Completed! -> Elapsed time:30.47s or 0.51min. | Estimated time: 71.10s or 1.19min
Status: 40% Completed! -> Elapsed time:40.63s or 0.68min. | Estimated time: 60.94s or 1.02min
Status: 50% Completed! -> Elapsed time:50.78s or 0.85min. | Estimated time: 50.78s or 0.85min
Status: 60% Completed! -> Elapsed time:61.24s or 1.02min. | Estimated time: 40.83s or 0.68min
Status: 70% Completed! -> Elapsed time:71.43s or 1.19min. | Estimated time: 30.61s or 0.51min
Status: 80% Completed! -> Elapsed time:81.65s or 1.36min. | Estimated time: 20.41s or 0.34min
Status: 90% Completed! -> Elapsed time:92.09s or 1.53min. | Estimated time: 10.23s or 0.17min
Status: 100% Completed! -> Elapsed time:102.27s or 1.70min. | Estimated time: 0.00s or 0.00min




참고. 모든 DataFrame을 분석하는 기능

가져온 혹은 생성된 DataFrame에서 수행해야 하는 몇 가지 기본 분석이 있습니다. 이러한 반복적인 단계를 수행하는 함수를 만들고 가져다 쓰면 됩니다(두 번째 스킬에 표시됨).
아래 코드를 참고하세요.

def analyse_df(df, corr_limit = 0.75):
    """모든 데이터 프레임 및 출력 결과 분석
    * 행 및 열의 수 확인, 중복 행 확인, 메모리 사용량, 데이터 유형, df.describe()호출
    * 모든 열에 대한 결측값 체크하여 숫자 혹은 비율로 반환
    * 열 상호간의 선형 상관관계 체크하여 반환(피어슨의 상관계수)
    Keyword arguments:
    df -- 임의의 데이터 프레임
    corr_limit -- 상관성 유무를 판단하는 기본 수치 0.75로 정의
    """   

    print('일반적인 정보:')
    print(f'{df.shape[0]} 행 {df.shape[1]} 수'
          f'\n{df.duplicated().sum()} 중복 열의 수'
          f'\n메모리 사용량: {df.memory_usage().sum()/(1024*1024):.2f}Mb')
    
    # 데이터 유형 체크
    int_list, float_list,object_list,bool_list,other_list =[[] for i in range(5)]
    for col in df.columns:
        if df[col].dtype == 'int64':
            int_list.append(col)
        elif df[col].dtype == 'float64':
            float_list.append(col)
        elif df[col].dtype == 'object':
            object_list.append(col)
        elif df[col].dtype == 'boolean':
            bool_list.append(col)
        else:
            other_list.append(col)
            
    for type_list,data_type in zip([int_list, float_list,object_list,bool_list,other_list],
                                   ['int64','float64','object','boolean','other']):
        if len(type_list)>0:
            print(f'\feature {data_type}: {type_list}')
            
    # 기술통계량 수치확인
    display(df.describe())
    
    # 각 칼럼에 대한 결측값 체크
    print('\n결측치 체크:')
    col_with_missing_counter = 0
    for col in df.columns:
        qnt_missing = df[col].isna().sum()
        if qnt_missing > 0:
            col_with_missing_counter +=1
            print(f'feature "{col}" 는 {qnt_missing}개의 결측치가 있습니다.({qnt_missing/df.shape[0]:.2%})')
    if col_with_missing_counter ==0 :
        print('데이터 셋에는 결측치가 없습니다.')
        
    # 열 간의 선형 상관관계 체크
    print('\n상관관계 체크:')
    df_corr = df.corr() # 열 간 상관관계를 나타내는 데이터 프레임
    ckecked_list =[] # 동일한 정보의 중복 출력 방지
    cols_with_correlation_counter = 0
    for col in df_corr.columns:
        ckecked_list.append(col)
        for i in range(len(df_corr)):
            if ((df_corr[col][i] > corr_limit or df_corr[col][i] < -corr_limit) and
                (df_corr.index[i] not in ckecked_list)):
                cols_with_correlation_counter += 1
                print(f'{df_corr.index[i]} 와 {col}의 상관계수 -> 피어슨의 상관계수 = {df_corr[col][i]:.2f}')         
    if cols_with_correlation_counter == 0:
        print('열 간의 상관관계는 없습니다.')

이상으로 생산성과 코드 품질 향상을 위한 5개의 Python Skill에 대해서 살펴 보았습니다. 이외에도 다양한 스킬이 있으면 공유해 주시면 좋을 거 같습니다.
감사합니다!!




답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다