본문 바로가기
국비 교육/데이터

[데이터 분석] Padas - 4

by 육츠 2023. 11. 11.
Contents 접기

그룹핑

특정 값을 기준으로 몇 개의 그룹으로 분할하여 처리하는 방식

df = pd.DataFrame({'A': ['chol','young']*3 +['chol'],
                   'B': [ 'one','one','two','one','two', 'two','one'],
                   'C': np.random.randn(7),
                   'D': np.random.randn(7)})
df

새로운 데이터프레임 생성

 

grouped = df.groupby('B')
print(grouped) 

for key,group in grouped:
    print('key:',key)
    print(group.head())
    print('-'*30)

 

그룹 객체 만들기 (key 가 one, two 인것 두가지를 출력한다.)

# 특정 그룹만 선택할 수도 있다.
one_group = grouped.get_group('one')
one_group.head()

 

그룹 연산

그룹별 통계치 구하기 : df.groupby(컬럼명).통계함수()
DataFrame 중 지정한 컬럼들에 대해 적용 가능 컬럼들에 대해 그룹별 통계치를 구함.

통계함수: sum, mean, std, var, min, max, count, quantile 등
df.df.groupby(컬럼명)[컬럼명 목록].통계함수() : 특정 컬럼들에 한해서만 통계함수 구함

df.groupby('B')[['C','D']].sum()

 

[실습] 파일 읽어와서 그룹 연산

data = pd.read_csv('./sales.csv')
data

 

판다스에도 파일을 읽어오는 함수가 존재한다. pandas.read_***()을 통해 파일의 데이터 형태에 맞춰 뒤에 글자가 변화한다.

 

# 부서별 매출액 합계
data.groupby('부서')[['매출액']].sum() 
# 시리즈['매출액'] -> 데이터프레임[['매출액']]
# 여러개의 컬럼을 그룹핑 조건으로 줄 수 있다.
data.groupby(['부서','연도'])[['매출액']].sum()

여러개의 컬럼을 그룹핑 조건으로 주어 매출액의 합계를 데이터 프레임으로 반환한다.

 

data.pivot_table(index = ['부서','연도'], values = '매출액', aggfunc='sum')

피봇테이블로도 가능하다.

 

Aggregation

df.groupby(컬럼명)[컬럼명].agg([통계함수 1, 통계함수 2,..])

# 부서별 최대 매출액 과 최소 매출액의 차이 계산 함수

def min_max(x): # x '매출액 컬럼'
    return x.max() - x.min()

grouped['매출액'].agg(min_max)
# 함수가 아닌 람다식 방법도 있음
grouped['매출액'].agg(['sum','mean','max','min'])

집계함수를 다중으로 두는 것도 가능하다.

 

시계열 데이터

pd.date_range()
start: 시작 날짜
periods : 생성할 timestamp 개수
freq: 시간 간격 
(D: 1일 간격(기본값), nD: n일 간격, w: 1주 간격, M: 월말 간격, MS: 월 초, Q: 분기 말, QS: 분기 초, A: 연말, AS: 연초)

dates = pd.date_range('20231110',periods= 6)
print(type(dates))
print(dates)

 

기본값 = 하루
DatetimeIndex(['2023-11-10', '2023-11-11', '2023-11-12', '2023-11-13', '2023-11-14', '2023-11-15'], dtype='datetime64[ns]', freq='D') 하루 간격으로 구해준다.

 

dates = pd.date_range('20230508', periods= 6 , freq='MS')
print(dates) # 다가오는 가장 가까운 월초를 시작값으로

DatetimeIndex(['2023-06-01', '2023-07-01', '2023-08-01', '2023-09-01','2023-10-01', '2023-11-01'], dtype='datetime64[ns]', freq='MS') : 가장 가까운 월초를 시작값으로 구해준다.

pd.to_datetime()

문자열 등 다른 자료형을 판다스 timestamp를 나타내는 datetime64 자료형으로 변환한다.

import pandas as pd
import numpy as np
date_list = ['2023-01-01','2023-02-01','2023-03-01'] # 문자열 형태
ts_date = pd.to_datetime(date_list)
print(ts_date) 
# DatetimeIndex(['2023-01-01', '2023-02-01', '2023-03-01'], dtype='datetime64[ns]', freq=None)

print(ts_date.year) # Index([2023, 2023, 2023], dtype='int32')
print(ts_date.month) # Index([1, 2, 3], dtype='int32')
print(ts_date.dayofweek) # Index([6, 2, 2], dtype='int32')

to_datetime() 에서 날짜만 따로 추출할 수 있는 모듈

dt 속성
datetime 타입으로 변환된 column(datetime 타입의 Series 객체) 에서 날짜 및 시간 정보를 추출하고 싶은 경우에는 dt 속성을 이용해야 한다.

df = pd.DataFrame(
    {'날짜': ['2023-01-01','2023-02-01','2023-03-01'],
     '매출': [1000, 2000, 3000]})
df.info()

df['날짜'] = pd.to_datetime( df['날짜'])
df.info() # object -> datetime64
# df['날짜'].year # err. df 날짜에서 가져오게 되면 Series 객체로 넘어오기 때문이다 
df['날짜'].dt.year # datetime 타입으로 변환 

# 1 월에 해당하는 매출액 가져오기
df[df['날짜'].dt.month == 1]

문자열로 되어있는 날짜는 datetime을 변경시켜 추출해와야 한다.

# 년 - 월 - 일 이 아닌 월 - 일 - 년 은 명시적으로 포맷을 지정해주어야 한다.

to_datetime() 의 format
format 속성은 지정한 포맷으로 변경하라는 의미가 아닌, 원본 데이터를 주어진 포맷에 맞춰 해석하여 변경하라는 의미
포맷 형식 참고 URL: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior

 

datetime — Basic date and time types

Source code: Lib/datetime.py The datetime module supplies classes for manipulating dates and times. While date and time arithmetic is supported, the focus of the implementation is on efficient attr...

docs.python.org

 

# 1.
values = {'dates': ['05032023','16032023','28032023'], # 일 - 월 - 년
         'status': ['Opened','Opened', 'Closed']} 
df = pd.DataFrame(values)
# df['dates'] = pd.to_datetime(df['dates']) # 날짜 형식이 정상적이지 않아 오류
df['dates'] = pd.to_datetime(df['dates'], format = '%d%m%Y') # 포맷 형식 지정 하는 것이지 변경하는 것은 아님
df.info() 
df # 올바른 날짜 형식으로 바뀜

# 2.
values = {'dates': ['05Mar2023','16Mar2023','28Mar2023'], # 일 - 월(글자) - 년
         'status': ['Opened','Opened', 'Closed']} 
df = pd.DataFrame(values)
df['dates'] = pd.to_datetime(df['dates'], format = '%d%b%Y') # %b  =Jan,Feb.. %B = January, Feburary...
df.info() 
df

# 3.
values = {'dates': ['20230305090300','20230316102520','20230328012015'], # 일 - 월 - 년
         'status': ['Opened','Opened', 'Closed']} 
df = pd.DataFrame(values)
df['dates'] = pd.to_datetime(df['dates'], format = '%Y%m%d%H%M%S') 
df.info() 
df # 올바른 날짜 형식으로 바뀜

포맷 형식을 지정해줌으로써 글자를 읽어올 수 있게 된다.

to_period()
DatetimeIndex 객체를 PeriodIndex 객체로 변환. freq: 변경하고자 하는 시간 간격

date_list = ['2023-01-01','2023-02-01','2023-03-01'] # 문자열 형태
ts_date = pd.to_datetime(date_list)
print(ts_date) 

# 주 간격으로 변환
# 원본 날짜를 포함하는 월- 일 까지의 주 간격 날짜 데이터 생성
ps = ts_date.to_period(freq = 'W') 
print(ps) # PeriodIndex(['2022-12-26/2023-01-01', '2023-01-30/2023-02-05', ...

ps = ts_date.to_period(freq = 'M') 
print(ps) # PeriodIndex(['2023-01', '2023-02', '2023-03'], dtype='period[M]')

 

함수 매핑

map() 함수의 이용

# Series 적용
s = pd.Series(np.arange(10))

s.map(lambda x:x**2) 
# 파이썬과 달리 연속형 객체를 넣어줄 필요가 없음.
# lambda x : 리턴식

map 함수를 이용한 데이터 수정
데이터 전처리 중에 하나인 labeling 이나 one-hot encoding 처리시 사용 ('명목형 데이터' -> '숫자형 데이터' 변환)

 

s = pd.Series(['A','B','B','A','C','B'])

map_data = {'A':0,'B':1,'C':2}
s.map(map_data) # 기존 명목형 -> 숫자형 : 라벨링

map 을 데이터 프레임에 적용


새로운 파일로 라벨링 실습

df = pd.read_csv('wages.csv')
# 데이터 확인
print(df.shape)
df.head()

df['sex'].unique()
df['sex'].value_counts()

# 성별 0, 1 로 라벨링
# 1. 딕셔너리 방법
df['sex'] = df['sex'].map({'male': 0, 'female': 1})

# 2. lambda식 방법
df['sex'] = df['sex'].map(lambda x: 0 if x == 'male' else 1)
df['race'].unique() # df['race'].value_counts()

# enumerate(), dictionary{}
dic_race = { v:i for i, v in enumerate(df['race'].unique())}
df['race'] = df['race'].map(dic_race)
df


df['sex'].replace(['male','female'],[0,1], inplace= True) 
# [오리지널 데이터], [뉴 데이터]
# 온전하게(정확하게) 일치

인종을 가지고 데이터 라벨링을 할때 인덱스에 맞춰 사용하기 위해서는  enumerate()를 사용한다.

replace() 를 이용한 데이터 수정
sr.replace() : 정확하게 일치하는 단어를 찾아 바꿔준다. (전체 문자열의 일치)
정규 표현식에 의해 매칭되는 문자를 찾아 바꾸기 위해서는 regex = True 속성을 지정한다.
sr.str.replace() : 일부 단어라도 일치하는 단어를 찾는다.

df = pd.read_csv('wages.csv')

df = pd.DataFrame({'Code': np.arange(3),
                   'Name': ['(S)Note Book','(S)Note Book','(S)PC'] })
df

# 1.
df['Name'] = df['Name'].replace('(S)','(M)')
df # 정확히 일치하는 것만 수정하기 때문에 변경되지 않는다.

# 2.
df['Name'] = df['Name'].replace('\([A-Z]\)','(M)', regex= True) # 정규표현식

# 3.
# str 모듈 사용
df['Name'] = df['Name'].str.replace('(S)','(M)')

 

apply() 함수의 사용

DataFrame() 에 사용자 정의 함수 적용하기

df = pd.read_csv('wages.csv')
df_sample = df[['earn','height','age']]
df_sample.head()

# 각 컬럼의 최대값 - 최소값 차이 계산
df_sample.apply(lambda x: x.max() - x.min())

# 각 컬럼의 최대값과 최소값을 Sries 객체로 반환
def f(x):
    return pd.Series([x.max(), x.min()], index=['max','min'])
df_sample.apply(f)

 

DataFrame 합치기

merge() 함수
두 DataFrame 에서 공통열(Primary Key(기본키), Foreign Key(외래키))이 필요, 공통키를 가지고 데이터를 병합
> 기본키: 관계형 데이터베이스에서 조의 식별자로 이용하기에 가장 적합한 것을 말한다.
> 외래키: 외래키는 두 테이블을 서로 연결하는 데 사용되는 키를 말한다.

# concat _ 행 또는 열로 합치는 것 (동일한 컬럼명/ 동일한 행명)
# merge() _ 아예 서로 다른 데이터 프레임을 합치는 것
df1 = pd.DataFrame(
    {'번호': [10,20,30,40,50,60,70],
     '이름': ['홍길동', '임꺽정','전우치','저팔계','사오정','손오공','삼장법사']}
)
df1

df2 = pd.DataFrame(
    {'번호': [10,10,20,40,50,40,40,70,80], # FK
     '금액': [1000,2000,3000,4000,5000,6000,7000,8000,9000]}
)
df2
# 병합의 기준 _ '번호' 라는 공통키를 가지고 두 데이터를 병합
# inner join : 양 쪽 데이터에 공통으로 있는 데이터에 대해 병합 ( 교집합과 같다 )
pd.merge(df1,df2)

# outer join: 한쪽에 존재하지 않는 값은 결측치 처리 (합집합과 같다)
pd.merge(df1,df2, how = 'outer')

# left inner join : 왼쪽 데이터프레임에 있는 컬럼값 기준 병합
pd.merge(df1,df2,how = 'left')

 

join on

df1 = pd.DataFrame(
    {'번호': [10,20,30,40,50,60,70],
     '이름': ['홍길동', '임꺽정','전우치','저팔계','사오정','손오공','삼장법사']}
)

df2 = pd.DataFrame(
    {'고객번호': [10,10,20,40,50,40,40,70,80], # FK
     '금액': [1000,2000,3000,4000,5000,6000,7000,8000,9000]}
)
# 명시적 지정 : 공통 컬럼이 없는 경우
pd.merge(df1,df2, left_on= '번호',right_on='고객번호')
# 중복된 컬럼을 하나 삭제시키면 됨.

중복되는 컬럼이 없을때는 명시적으로 합칠 것을 정해주고, 이후 같은 성질을 가진 것 중에 하나를 지운다.