본문 바로가기
[파이썬]/데이터 분석

[데이터 분석] 데이터 분석을 위한 데이터 가공(전처리)

by sung min_Kim 2023. 12. 3.


 본 글에서는 "분석을 위한 데이터 가공하기"에 관한 내용을 다룰 것이다. 차례와 사용 툴은 아래와 같다.


 [차례]
 첫 번째, 데이터 가공(전처리)란
 두 번째, 분석에 필요한 시나리오 작성하기 (분석 목적, 주제 등 포함)

 세 번째, 사용할 데이터 검증하기
 네 번째, 분석에 필요한 데이터 가공(전처리)하기
 다섯 번째, 생성한 프로세스를 통해 하나의 통합 파일로 생성하기

 [사용 툴]
- Jupyter notebook(웹 기반 대화형 코딩 환경)

 


· 데이터 가공, 또는 전처리


 데이터 가공, 또는 데이터 전처리는 같은 의미로 사용되는데, 이 용어들은 원시 데이터(초기 상태의 데이터)를 분석이 가능한 형태로 변환하는 과정을 가리킨다. 이는 데이터 분석을 수행하기 전에 반드시 거쳐야 하는 단계이다.
 
 이 과정에서는 누락된 값 처리, 이상치 제거, 데이터 표준화 및 정규화, 데이터 유형 변화 등이 포함될 수 있다. 데이터 가공(전처리)은 데이터의 품질을 향상하고, 분석의 정확도를 높이는 등, 데이터 분석의 효과를 높이기 위한 중요한 과정이다.

 


· 시나리오


 이 전에 컬럼명(한글) 변경작업을 수행한 "포항시 BIS 교통카드 사용내역 데이터"를 바탕으로 데이터 가공(전처리)를 진행할  것이다. 해당 작업을 수행할 때에는 분석 목적에 맞지 않는 불필요한 데이터를 제외하고, 분석에 필요한 데이터만을 선별하여 사용하는 것이 일반적이라 한다. 따라서 전에 위 데이터를 기반으로 생성하였던 'df_bus_card_org' 데이터프레임 객체를 검증(이상치, 결측치 확인)한 뒤, 분석에 필요한 데이터를 선별하여 , 데이터 가공(전처리)을 진행할 것이다.

 우선 어떠한 목적을 가지고 분석을 수행할 건지에 대한 분석 목적을 정의하고, 이에 맞는 분석 주제를 설정하도록 한다.
 분석 목적은 "포항시 버스 이용량 및 버스 내 체류시간 분석"으로 정의하고, 이에 맞는 분석 주제를 설정할 것이다.
 분석 주제
는 대 주제와 소 주제로 나눌 것이며, 대 주제는 "포항시 버스 이용량 분석"으로, 소 주제로는 "기준월, 일, 시간 단위로 버스 이용량과 버스 내 체류시간 분석"을 정의할 것이다.


 이후에는 분석에 필요한 데이터를 선별한다. "버스 이용량" 분석은 '승차정류장'과 '하차정류장'의 데이터를 활용하여, 각 정류장에서의 승하차 인원수를 계산하여 진행할 것이다. 또한, "버스 내 체류시간" 분석은 '승차시각'과 '하차시각'의 데이터를 이용하여, 승차와 하차 사이의 시간 차를 계산할 것이다.

 이 과정을 거쳐 생성된 샘플 프로세스를 이용하여, 개별적으로 저장된 "포항시 BIS 교통카드 사용내역 데이터" 파일들을 하나의 파일로 통합할 것이다.

 본 글에서는 "버스 이용량"과 "버스 내 체류시간"에 대한 데이터 분석을 수행하기 전 단계인 데이터 가공(전처리) 과정까지 만을 다룰 것이고, 이 데이터를 사용하여 시각화를 통한 데이터 분석은 다음 글에 작성할 예정이다.

 


· 데이터 검증하기

 

데이터프레임 복제본 생성

 데이터 가공(전처리)을 수행하기 전에, 'copy()' 함수를 사용하여 원본 데이터를 보존하도록 하자. 이유는 원본 데이터로 가공 작업을 수행하면, 나중에 원본 데이터가 필요할 때 다시 불러오는 것이 번거롭거나, 이전 상태로 되돌리기가 어려워질 수 있기 때문이다. 따라서, 원본 데이터는 그대로 두고, 복제한 데이터프레임에서 작업을 진행하도록 한다.

 

# 데이터프레임 복제본 생성
df_bus_card_kor = df_bus_card_org.copy()
df_bus_card_kor.head(1)

데이터프레임 복제본


 위 데이터프레임의 결측치와, 이상치를 확인하는 검증 단계를 수행하도록 한다.

 

검증_결측치 확인
# 결측치 데이터 확인
df_bus_card_kor.info()

 

info() => 결측치 확인


 전체 '16185'개의 행(RangeIndex) 중, '승객연령' 열에서 '21'개의 결측치('RangeInedx' - 'non-null')가 발견되었다. 이 결측치들은 아래 "데이터 가공" 섹션에서 처리할 예정이다.

 

검증_이상치확인
# 이상치 확인
df_bus_card_kor.describe()

describe => 이상치 확인

 이상치 확인 결과, 표준편차(std)와 중앙값(50%)의 일반적인 범위에서 크게 벗어나는 값이 없음을 확인하였다. 또한, 최소값(min)에서 마이너스 값을 가진 데이터도 발견되지 않았다. 따라서, 이상치 데이터의 존재는 매우 적을 것으로 판단된다.

 

 


· 분석에 필요한 데이터 가공(전처리)하기

 

분석에 필요한 컬럼 추출하기
# 분석에 필요한 컬럼 추출
df_bus_card = df_bus_card_kor[["승차시각", "하차시각", "승객연령", "환승여부",
                               "추가운임여부", "승차정류장", "하차정류장"]].copy()
df_bus_card.head()


 'copy()' 함수를 사용해, 분석에 필요한 컬럼을 갖는 복제본 'df_bus_card' 데이터프레임을 생성하였다. 이 데이터프렘을 사용해 데이터 가공(전처리)을 진행할 것이다.

분석에 필요한 컬럼들 추출

 

결측치 처리

 위의 섹션에서 발견한 결측치에 대한 가공(전처리) 작업을 진행하려 한다. '승객연령'이 어떤 값을 나타내는지 확인할 후에 결측치를 처리할 것이다.

 

# 열의 각 값의 빈도수 조회
df_bus_card["승객연령"].value_counts()

 

value_counts() => 열에 있는 각 값의 빈도수 계산

 판다스 라이브러리의 'value_counts()' 함수를 사용하여 '승객연령' 열의 각 값의 빈도수를 확인하였다. 이를 통해 '승객연령' 열이 '일반, 청소년, 어린이'의 분류를 나타내는 것을 알 수 있다. 이 분류형 데이터에는 순서나, 수치적인 의미가 없으므로, 결측치를 '0'이 아닌 '알 수 없음'으로 대체하려 한다.

 

# 결측치 처리
df_bus_card["승객연령"] = df_bus_card["승객연령"].fillna("알 수 없음")
df_bus_card.info()

 
 결측치를 처리할 때에는 판다스에서 제공하는 'fillna()' 함수를 사용한다. 이 함수의 인자로는 결측치를 대체하는 값을 입력한다. 이를 통해 "승객구분" 컬럼의 결측치 데이터의 값은 "알 수 없음"으로 대체될 것이다.

 위 코드를 실행한 결과, 전체 행의 개수(RangeIndex)와 '승객구분' 컬럼의 결측치가 아닌 값(non-null)의 수가 일치하는 것을 볼 수 있다. 이로써, 결측치가 더 이상 존재하지 않음을 확인할 수 있다.

fillna() => 결측치 데이터 처리

 

컬럼명 변경

 '일반, 청소년, 어린이'의 분류 데이터를 나타내는 '승객연령' 열의 의미는 적절하지 않은 것 같아, 이를 '승객구분'으로 수정하려 한다. 판다스 데이터프레임의 컬럼이나 행의 이름을 수정하고자 할 때는, 'rename()' 함수를 사용한다.

 

# 컬럼명 변경
df_bus_card = df_bus_card.rename(columns={"승객연령" : "승객구분"})
df_bus_card.head(1)


 'rename()'함수는 딕셔너리 형태의 매개변수를 사용하여, 기존의 컬럼명(키)을 새로운 컬럼명(값)으로 변경한다. 여기서는 '승객연령'을 '승객구분'으로 변경한다. 

rename() => 컬럼명 변경


 그리고 판다스에서 열의 이름을 나타내는 데이터 유형은 기본적으로 시리즈(series) 타입이지만, 딕셔너리 타입처럼 사용할 수 있다. 이는 판다스가 딕셔너리와 유사한 인덱싱 방식을 지원하기 때문이다. 따라서, 열의 이름을 변경하는 등의 작업에서 시리즈 타입의 데이터는 딕셔너리 타입의 매개변수로서 사용될 수 있다.  

 

데이터 유형 변경

 '승차시각'과 '하차시각'의 데이터 유형은 정수형(int) 타입이다. 이 데이터는 특정한 형식(20200102051049와 같은 YYYYMMDDHHMMSS 형식)으로 저장되기 때문에, 날짜와 시간에 관련된 다양한 작업(예: 연, 월, 일, 시간, 분 등으로 나누기)을 수행하기 어렵다.

 따라서, '승차시각'과 '하차시각'을 문자열(str) 타입으로 변경한 후, 이를 다시 날짜시간형(datetime) 타입으로 변경하는 과정이 필요하다. 이렇게 하면 날짜와 시간에 관련된 다양한 작업을 수행할 수 있게 된다. 예를 들어, 날짜 및 시간의 각 부분을 추출하거나, 연산을 수행하는 등의 작업이 가능해진다.

 

 

  • Int → Str

 'astype()' 함수를 사용해 '승차시각'과 '하차시각'의 데이터 유형을 'int'에서 'str'로 변경하도록 한다. 데이터 유형을 변경하고자 할 때에는 판다스에서 제공하는 'astype()' 함수를 사용해 변경해 주도록 한다. 이 함수를 사용하면 int, float, str 등 다양한 타입의 데이터를 변환할 수 있다. 이 함수에 딕셔너리를 매개변수로 전달하면, 셔너리의 키(key)에 해당하는 열의 데이터 타입을 딕셔너리 값(value)에 해당하는 타입으로 변경한다. 

 

# 데이터 유형 변경 (int => str)
df_bus_card = df_bus_card.astype({"승차시각" : "str", 
                                  "하차시각" : "str"})
df_bus_card.info()


 즉, 위의 코드를 실행하면 'df_bus_card' 데이터프레임의 '승차시각'과 '하차시각' 열의 모든 값들의 데이터 유형을 문자열로 변환된다.

astype() => 데이터 유형 변경

 

 

  • Str → Datetime

 문자열(str) 데이터 유형을 날짜타입형(datetime)으로 변경하려면, 판다스에서 제공하는 'to_datetime()' 함수를 사용한다. 이 함수는 문자열, 정수, 실수 등 다양한 형태로 날짜와 시간을 표현하는 데이터를 'datetime' 유형으로 변환하는 데 사용된다. 변환할 데이터를 매개변수로 받아, 리스트, 시리즈 등의 다양한 형태의 입력을 처리할 수 있다.

# 데이터 유형 변경 (str => datetime)
df_bus_card["승차시각"] = pd.to_datetime(df_bus_card_kor.loc[:, "승차시각"])
df_bus_card["하차시각"] = pd.to_datetime(df_bus_card_kor.loc[:, "하차시각"])

df_bus_card.info()

 

 위 코드를 실행하면, 'df_bus_card_kor' 데이터프레임의 '승차시각'과 '하차시각' 열'의  모든 행에 대한 데이터 유형을 'datetime'으로 변환하고, 이를 'df_bus_cart' 데이터프레임의 '승차시각'과 '하차시각'에 할당한다.

 

분석에 필요한 컬럼 생성하기

 

  1. 첫 번째, '버스 내 체류시간(분)' 컬럼 생성하기

    # 버스내체류시간(분) 컬럼 생성
    df_bus_card["버스체류시간(분)"] = round((df_bus_card["하차시각"] - 
                                            df_bus_card["승차시각"]).dt.total_seconds()/60, 2)
    df_bus_card.head()​

     위의 코드에서는
     'df_bus_card' 데이터프레임의 '하차시각'과 '승차시각'의 열 데이터 간의 차이를 계산한다. 이때, 두 값은 'datetime' 타입으로, 이 연산의 결과는 시간 차이를 나타내는 'timedelta' 객체가 된다.

      'dt'는 판다스의 데이터프레임이나 시리즈에서 'datetime' 관련 연산을 수행할 수 있도록 도와주는 'accessor'이다. 이를 통해 ' timedalta' 객체를 다룰 수 있다.

     'total_seconds()' 함수는 'timedelta' 객체를 초 단위로 변환하는 함수이다. 이 메서드의 결과를 '60'으로 나눔으로써 시간 차이를 분 단위로 변환한다.

     'round()' 함수는 반올림 연산을 수행하는 함수로, 결과값을 소수점 둘째 자리까지 반올림한다.

     마지막으로, 계산된 버스 체류시간을 '버스체류시간(분)'이라는 새로운 열로 데이터프레임에 추가한다.

     따라서, 위 코드를 실행하면, '승차시각'과 '하차시각'의 차이를 분 단위로 계산하여 '버스체류시간(분)'이라는 새 열을 생성할 수 있다.

    '버스체류시간(분)' 열 생성



  2. 두 번째, '기준년도, 기준월, 기준일, 기준시간, 기준시간(분)' 컬럼 생성하기

    # - 기준년도
    df_bus_card["기준년도"] = df_bus_card["승차시각"].dt.year
    
    # - 기준월
    df_bus_card["기준월"] = df_bus_card["승차시각"].dt.month
    
    # - 기준일
    df_bus_card["기준일"] = df_bus_card["승차시각"].dt.day
    
    # - 기준시간
    df_bus_card["기준시간"] = df_bus_card["승차시각"].dt.hour
    
    # - 기준시간(분)
    df_bus_card["기준시간(분)"] = df_bus_card["승차시각"].dt.minute
    
    df_bus_card.head()

     '승차시각' 열의 데이터 유형이 'datetime'인 데이터에서 '연,월,일,시,분' 정보를 추출하여 새로운 열을 생성하는 작업을 수행한다. 이때 사용되는 'dt'는 판다스의 'accessor'로, 'datetime' 타입의 데이터에서 다양한 정보를 쉽게 추출할 수 있게 한다. 따라서, 위의 코드를 실행하면, '기준년도, 월, 일, 시간, 분'에 대한 새로운 열이 생성된다.

    '기준년도, 월, 일, 시간, 분' 열 생성

 


· 생성한 프로세스를 통해 하나의 통합 파일로 생성하기


 최종적으로, 위의 과정들을 수행하여 데이터 분석에 사용하고자 하는 '한 건의 샘플 프로세스'를 생성하였다. 이를 이용하여 개별적으로 저장된 "포항시 BIS 교통카드 사용내역 데이터" 파일들을 하나의 파일로 통합하기 위한 작업을 진행할 것이다.

 

  •  "포항시 BIS 교통카드 사용내역 데이터" 파일들을 하나의 파일로 통합하기

df_bus_card_tot = pd.DataFrame()

for i in range(0, 80, 1) :
    file_path = f"./01_data/org/trfcard({i})/trfcard.csv"
    df_bus_card_org = pd.read_csv(file_path)
    
    
    # ... 수행한 데이터 가공(전처리) 코드들을 나열 ...
    
    
    df_bus_card_tot = pd.concat([df_bus_card_tot, df_bus_card], 
                                 axis=0, ignore_index=True)


 위 코드는 0번부터 79번까지의 폴더를 순회하면서, 각 폴더 내의 'trfcard.csv' 파일을 읽어 데이터프레임 객체로 변환한다. 이때, 각 파일에서 읽은 데이터에 대해 필요한 가공(전처리) 작업을 진행한다. 가공(전처리)된 각 데이터프레임은 'df_bus_car_tot'라는 데이터프레임에 병합되며, 이는 'pd.concat()' 함수를 사용하여 수행된다.
 결과적으로, 위의 코드는 다양한 폴더에 흩어져 있는 'trfcard.csv' 파일들을 효율적으로 처리하고, 하나의 데이터프레임 'df_bus_card_tot'으로 통합하는 작업을 수행한다.

 통합한 데이터프레임은 판다스에서 제공하는 'to_csv()' 함수를 사용하여 저장하도록 하자.

# 데이터프레임 저장
df_bus_card_tot.to_csv("./01_data/all/df_bus_card_tot.csv", index=False)

 

'to_csv()' => 파일로 저장

 



이 모든 과정을 통해, 'trfcard.csv' 파일들을 효과적으로 처리하고,
분석에  필요한 정보만을 추출하여 가공하였다.

이렇게 생성된 데이터를 바탕으로 더욱 신뢰성 있는 결과를 도출할 수 있다.

이 과정에서 중요한 것은, 데이터의 품질을 검증하고, 필요한 데이터를 추출하는 것이다.
이를 통해 분석의 정확성을 보장받을 수 있었다.

또한, 이를 통해 다양한 폴더에 흩어져 있는 'trfcard.csv' 파일들을 하나로 효율적으로 통합할 수 있었다.


이후에는 현재 가공된 데이터를 바탕으로 시각화를 통한 데이터 분석을 진행할 것이다.