데이터 분석 목적에 따라, 컬럼 <-> 행 변화로 테이블 형태 자유롭게 변형 가능

Table 1. 피어슨 테스트 적합

  Pregnant Not pregnant
Male 0 5
Female 1 4

Table 2. regression에 적합

Gender Pregnant Count
Male Yes 0
Male No 5
Female Yes 1
Female No 4

-열: variable, 행: observation 나타냄. (값은 범주형/연속형)
-분석하고자 하는 방향에 따라 열 또는 행을 다른 형태로 변형할 수 있어야한다.

import pandas as pd
from io import StringIO
A_csv = """city,year,cases
A,2010,745
B,2010,37737
C,2010,212258
A,2020,2666
B,2020,80488
C,2020,213766"""

with StringIO(A_csv) as fp:
    A = pd.read_csv(fp)
B_csv = """city,year,population
A,2010,1235795
B,2010,56837737
C,2010,256812258
A,2020,2665476
B,2020,82014878
C,2020,2117376956"""

with StringIO(B_csv) as fp:
    B = pd.read_csv(fp)

.merge()

-병합: 두 DF를 각 데이터에 존재하는 고유값(key)를 기준
-디폴트 설정: pd.merge(df_left, df_right, how=’inner’, on=None)
-outer 합집합, inner 교집합 의미

C = A.merge(B, on=['city', 'year'])  # 공통 key 두개 기준, 교집합 조인
C
city year cases population
0 A 2010 745 1235795
1 B 2010 37737 56837737
2 C 2010 212258 256812258
3 A 2020 2666 2665476
4 B 2020 80488 82014878
5 C 2020 213766 2117376956

.join()

inner-join(A, B)디폴트: on-keys가 매칭되는 행만 남김
outer-join(A, B): 합집합, on-keys가 매칭되지 않으면 NaN값으로 채움
left-join(A, B): A의 모든 행을 남기고 B는 A와 매칭되는 on-keys의 행만 결합
right-join(A, B): B의 모든 행을 남기고 A는 B와 매칭되는 on-keys의 행만 결합

with StringIO("""a,b,c
bug,4,x
rug,2,x
lug,1,x
mug,3,x""") as fp:
    D = pd.read_csv(fp)

with StringIO("""a,b,d
hug,-1,y
smug,-2,y
rug,-3,y
tug,-4,y
bug,4,y""") as fp:
    E = pd.read_csv(fp)
D.merge(E, on=['a', 'b'])   # inner-join
a b c d
0 bug 4 x y
D.merge(E, on=['a', 'b'], how='outer')  # outer-join
a b c d
0 bug 4 x y
1 rug 2 x NaN
2 lug 1 x NaN
3 mug 3 x NaN
4 hug -1 NaN y
5 smug -2 NaN y
6 rug -3 NaN y
7 tug -4 NaN y
D.merge(E, on=['a', 'b'], how='left')  # left-join
a b c d
0 bug 4 x y
1 rug 2 x NaN
2 lug 1 x NaN
3 mug 3 x NaN
D.merge(E, on=['a', 'b'], how='right') # right-join
a b c d
0 hug -1 NaN y
1 smug -2 NaN y
2 rug -3 NaN y
3 tug -4 NaN y
4 bug 4 x y

apply()

CC=C.copy()
CC['year']=CC['year'].apply(lambda x: "`{:02d}".format(x % 100))
CC
city year cases population
0 A `10 745 1235795
1 B `10 37737 56837737
2 C `10 212258 256812258
3 A `20 2666 2665476
4 B `20 80488 82014878
5 C `20 213766 2117376956
# x % 100 :  x를 100으로 나눈 나머지
103%100
3
# {:02d} : 최소 두자리수 만들기
"{:02}".format(2)
'02'

연습문제

# apply함수를 이용해서 비율을 구하기
def cal(F):
    assert 'cases' in F.columns and 'population' in F.columns
    ###
    def cal(F):
        return F['cases']/F['population']
    G = F.copy()
    G['proportion'] = G.apply(cal, axis=1)   # axis=1 열방향 적용
    return G
# 정렬하기
def organize(F):
    
    # 컬럼명을 이름순으로 정렬
    var_names = sorted(F.columns)
    G = F[var_names].copy()
    
    # 각 행을 오름차순으로 정렬
    G.sort_values(by=var_names, inplace=True)
    
    # 인덱스를 재정렬
    G.reset_index(drop=True, inplace=True)
    
    return G
# 두 테이블이 같은지 확인하는 함수 만들기 v.1
def check_equivalent(A, B):
    
    o_A = organize(A)
    o_B = organize(B)
    equal = (o_A == o_B)
    return equal.all().all()   # all()이 하나면 컬럼별로 T/F 출력
a=pd.DataFrame({'a':[1,2], 'b':[5,7]})
b=pd.DataFrame({'a':[1,2], 'b':[4,7]})
(a==b).all()   # all()이 하나면 컬럼별로 T/F 출력
a     True
b    False
dtype: bool

.all()
반복 가능 객체의 모든 요소가 True면 True 반환. 하나라도 False면 False 반환
(dictionary의 경우, key가 True인지 체크)
.any()
반복 가능 객체의 요소가 하나라도 True면 True 반환

# 두 테이블이 같은지 확인하는 함수 만들기 v.2
def check_equivalent(A, B):
    for c in A.columns:
        total = list(zip(A[c], B[c]))
        for t in total:
            if t[0] == t[1]:
                return True
            else:
                return False

Melting

컬럼을 행으로 바꾸기

a = pd.DataFrame({'cities':['a','b','c'], 2020:[123,456,789], 2021:[751,849,621]})
b = pd.DataFrame({'cities':['a','a','b','b','c','c'], 'year':[2020,2021,2020,2021,2020,2021],'cases':[123,751,456,849,789,621]})
display(a)
display(b)   # a 테이블을 b 테이블로 형태 변화
cities 2020 2021
0 a 123 751
1 b 456 849
2 c 789 621
cities year cases
0 a 2020 123
1 a 2021 751
2 b 2020 456
3 b 2021 849
4 c 2020 789
5 c 2021 621
def melt(df, list_col, key, value):
    # list_col: 바꿀 컬럼명들, key: 새로운 컬럼명, value: 값을 가져갈 컬럼명
    keep_vars = df.columns.difference(list_col) # 바꿀 컬럼명 제외한 나머지 컬럼들
    melted_sections = [] # 빈 리스트
    for c in list_col:   # 바꿀 컬럼 하나하나에 대해서 
        melted_c = df[keep_vars].copy() # DF와 동일한 컬럼들만 카피
        melted_c[key] = c   # melted_c['year'] = 2020 또는 2021
        melted_c[value] = df[c] # melted_c['cases'] = df[2020] 또는 df[2021]
        melted_sections.append(melted_c) # 빈 리스트에 확장
    melted = pd.concat(melted_sections) # 행 방향으로 데이터 결합
    return melted

Casting

행을 병합

a = pd.DataFrame({'cities':['a','a','b','b','c','c'], 'year':[2020,2021,2020,2021,2020,2021],'cases':[123,751,456,849,789,621]})
b = pd.DataFrame({'cities':['a','b','c'], 2020:[123,456,789], 2021:[751,849,621]})
display(a)
display(b)   # a 테이블을 b 테이블로 형태 변화
cities year cases
0 a 2020 123
1 a 2021 751
2 b 2020 456
3 b 2021 849
4 c 2020 789
5 c 2021 621
cities 2020 2021
0 a 123 751
1 b 456 849
2 c 789 621
def cast(df, key, value, join_how='outer'):
    fixed_vars = df.columns.difference([key, value])
    tibble = pd.DataFrame(columns=fixed_vars) # 빈 프레임
    # key, value: input df의 컬럼 중 새로운 테이블의 컬럼명/value가 될 값 

    cols = df[key].unique()  # df['year']의 고유값들
    for c in cols:
        df_c = df[df[key]==c] # df['year']==2020 or 2021인 
        del df_c[key]  #변수까지 완전 삭제
        df_c = df_c.rename(columns = {value:c}) #value에서 c로 컬럼명 변경
        tibble = tibble.merge(df_c, on=list(fixed_vars), how=join_how)
        # df의 남은 컬럼 기준 병합
    return tibble
# 한 컬럼의 값을 두개 컬럼으로 나누기
a = pd.DataFrame({'product':['a','b'], 'year':[2020,2021],'rate':['123/751', '521/879']})
b = pd.DataFrame({'product':['a','b'], 'year':[2020,2021],'purchase':[123,521],'total':[751,879]})
display(a)
display(b)  # a 테이블을 b 테이블로 형태 변화
product year rate
0 a 2020 123/751
1 b 2021 521/879
product year purchase total
0 a 2020 123 751
1 b 2021 521 879
import re

def default_splitter(text): # 숫자를 string으로
    fields = re.findall('(\d+\.?\d+)', text)
    return fields

def separate(df, key, into, splitter=default_splitter):
    # key 컬럼을 into 컬럼들로 변형 (예: key 123/456 => into 123, intoo 456)
    #참고: http://stackoverflow.com/questions/16236684/apply-pandas-function-to-column-to-create-multiple-new-columns

    def apply_splitter(text):
        fields=splitter(text)  # % 나 $ 등 빼고 모두 string으로
        return pd.Series({into[i]:v for i, v in enumerate(fields)})
        # 바꿀 컬럼의 값을 인덱스: 값을 가진 dic -> 시리즈로
    keep_vars=df.columns.difference([key]) # 바꿀 컬럼 빼고 나머지
    t1=df[keep_vars].copy() 
    t2=df[key].apply(apply_splitter) # 바꿀 컬럼을 apply 이용해서 변형
    return pd.concat([t1, t2], axis=1) # 동일한 형태 DF를 열 방향으로 합치기
# 두 컬럼의 값을 한개 컬럼으로 합치기
a = pd.DataFrame({'product':['a','b'], 'year':[2020,2021],'purchase':[123,521],'total':[751,879]})
b = pd.DataFrame({'product':['a','b'], 'year':[2020,2021],'rate':['123/751', '521/879']})
display(a)
display(b)  # a 테이블을 b 테이블로 형태 변화
product year purchase total
0 a 2020 123 751
1 b 2021 521 879
product year rate
0 a 2020 123/751
1 b 2021 521/879
def str_join_elements(y, sep=""):
    assert type(sep) is str
    yy = sep.join([str(yi) for yi in y])  # string으로 바꾸고 join
    return yy

def unite(df, old_col, new_col, combine=str_join_elements):
    # 참고: http://stackoverflow.com/questions/13331698/how-to-apply-a-function-to-two-columns-of-pandas-dataframe
    keep = df.columns.difference(old_col)
    d = df[keep].copy()
    d[new_col]=df[old_col].apply(combine, axis=1)
    return d
# NaN값 제거하고 모든 컬럼값을 integer로 변형하기
from math import isnan  # NaN 값 체크
def remove_nan(df):
    df2 = df[df['a'].apply(lambda x: not isnan(x))] # NaN이 아닌 값만 선택
    df2['a'] = df2['a'].apply(lambda x: int(x)) # a 컬럼의 값을 integer로 변형
    return df2
# 예: 'new_sn_m065'등의 값을 가진 'who'컬럼을 여러 컬럼으로 나누기
import re
def divide_who(text):
    m = re.match("^new_?(rel|sn|sp)_(f|m)(\\d{2,4})$", text)
    if m is None or len(m.groups()) != 3: # None이거나 3개 미만일 때
        return ['', '', '']

    fields = list(m.groups()) # 3개 그룹
    if fields[1] == 'f':    
        fields[1] = 'female'
    elif fields[1] == 'm':
        fields[1] = 'male'
    if fields[2] == '020':
        fields[2] = '0-20'
    elif fields[2] == '60':
        fields[2] = '60+'
    elif len(fields[2]) == 4 and fields[2].isdigit(): # 길이 4이고 digit이면
        fields[2] = fields[2][0:2] + '-' + fields[2][2:4] # 첫~두번째값-세번째값
    return fields

# df3 = separate(df,
#                 key='who', # 바꿀 컬럼
#                 into=['type', 'gender', 'age_group'], # 세 컬럼으로
#                 splitter=divide_who)