본문 바로가기

Python/Data Analysis

Data Analysis / pandas / DataFrame(4)

< DataFrame의 결합 : merge >

# Database의 Table join 기능
# DataFrame 두개를 연결시켜서 새로운 DataFrame을 만들어보자
import numpy as np
import pandas as pd

data1 = {'학번' : [1,2,3,4],
        '이름' : ['홍길동', '박동훈', '이순신', '강감찬'],
        '학년' : [2,4,1,3]}

data2 = {'학번' : [1,2,4,5],
        '학과' : ['CS', 'MATH', 'MATH', 'CS'],
        '학점' : [3.4,  2.9, 4.5, 1.2]}

df1 = pd.DataFrame(data1)
df2 = pd.DataFrame(data2)

# merge() 함수를 사용해서 결합 수행
# 옵션
# on : 어떤 column을 기준으로 결합할지
# how : 어떤 방식으로 merge 할 것인지

# 1. inner join
# 기준 column에 대해서 교집합
result = pd.merge(df1, df2, on='학번', how='inner')
display(result)

# 2-1. full outer join
# 기준 column에 대해서 합집합
# 데이터가 비는 곳은 NaN으로 처리
result = pd.merge(df1, df2, on='학번', how='outer')
display(result)

# 2-2. left outer join
# 왼쪽 DataFrame 항목을 모두 넣고, 해당하는 오른쪽 DataFrame을 붙임
# 해당하는 항목이 없는 경우 NaN로 처리
result = pd.merge(df1, df2, on='학번', how='left')
display(result)

# 2-3. right outer join
# 오른쪽 DataFrame 항목을 모두 넣고, 해당하는 왼쪽 DataFrame을 붙임
# 해당하는 항목이 없는 경우 NaN로 처리
result = pd.merge(df1, df2, on='학번', how='right')
display(result)

############################################################################

# 만약 column 명이 서로 다르다면?
data1 = {'학번' : [1,2,3,4],
        '이름' : ['홍길동', '박동훈', '이순신', '강감찬'],
        '학년' : [2,4,1,3]}

# 데이터의 학번 부분이 이름이 다르다면?
data2 = {'학생학번' : [1,2,4,5],
        '학과' : ['CS', 'MATH', 'MATH', 'CS'],
        '학점' : [3.4,  2.9, 4.5, 1.2]}

df1 = pd.DataFrame(data1)
df2 = pd.DataFrame(data2)

# left_on, right_on 속성으로 각 테이블의 컬럼을 지칭해서 매칭 가능
result = pd.merge(df1, df2, left_on='학번', right_on='학생학번', how='inner')
display(result)

############################################################################

# column과 index를 이용한 merge
data1 = {'학번' : [1,2,3,4],
        '이름' : ['홍길동', '박동훈', '이순신', '강감찬'],
        '학년' : [2,4,1,3]}

# 이 경우는 학번항목이 아예 없음
data2 = {'학과' : ['CS', 'MATH', 'MATH', 'CS'],
        '학점' : [3.4,  2.9, 4.5, 1.2]}

df1 = pd.DataFrame(data1)
# DataFrame을 생성할 때, 학번을 index로 사용한 경우
df2 = pd.DataFrame(data2,
                  index=[1,2,4,5]) # 학번을 index로 사용
# df1의 '학번' column과 df2의 index를 매칭시켜 주어야 한다.

# _on으로 column을 지칭했다면, _index로 index 사용을 설정할 수 있다.
result = pd.merge(df1, df2, left_on='학번', right_index=True, how='inner')
display(result)

# loc[]는 사용자가 지정한 인덱스가 있다면 그에 대해서 탐색
# 없으면 기본적으로 할당된 숫자 인덱스를 탐색
# 0, 1, 3 뿐이므로 error
# result.loc[2]  # error
display(result.loc[3])  # OK - Series

# iloc[]는 기본적으로 할당된 숫자 인덱스를 탐색
# 0, 1, 2 이다.
display(result.iloc[2]) # OK - Series
# result.iloc[3] # error

############################################################################

# index와 index를 이용한 merge
import numpy as np
import pandas as pd

data1 = {'이름' : ['홍길동', '박동훈', '이순신', '강감찬'],
        '학년' : [2,4,1,3]}

data2 = {'학과' : ['CS', 'MATH', 'MATH', 'CS'],
        '학점' : [3.4,  2.9, 4.5, 1.2]}

df1 = pd.DataFrame(data1, index=[1,2,3,4])
df2 = pd.DataFrame(data2, index=[1,2,4,5])

result = pd.merge(df1, df2, left_index=True, right_index=True, how='inner')
display(result)

< Series/DataFrame 연결 : concatenation >

# Series의 1차원, 2차원 연결
import numpy as np
import pandas as pd

s1 = pd.Series([0, 1], index=['a','c'])
s2 = pd.Series([4,3,2], index=['b','c','e'])
s3 = pd.Series([5,6], index=['f','g'])

# Series는 1차원 vector
# concat() 함수로 연결작업 수행
# 연결할 Series/DataFrame들을 리스트형태로 전달하고, 축방향을 지정

# 1. 행방향으로 연결하는 방법
print(pd.concat([s1, s2, s3], axis=0)  # Series를 1차원으로 연결 -> Series
# a    0
# c    1
# b    4
# c    3
# e    2
# f    5
# g    6
# dtype: int64

# 2. 열방향으로 연결하는 방법
# index를 조심해야 한다.
# column 이름은 붙이는 순서에 따라 0, 1, 2... 로 default로 매겨진다.
# concat() 함수에 sort 옵션을 주어 index 기준으로 정렬할 것인지 결정
# 붙였을 때 해당하는 값이 없는 부분은 NaN으로 설정
display(pd.concat([s1,s3,s2], axis=1, sort=True)  # -> DataFrame
#     0    1    2
# a  0.0  NaN  NaN
# b  NaN  NaN  4.0
# c  1.0  NaN  3.0
# e  NaN  NaN  2.0
# f  NaN  5.0  NaN
# g  NaN  6.0  NaN

######################################################################

# DataFrame의 연결
import numpy as np
import pandas as pd

df1 = pd.DataFrame(np.arange(6).reshape(3,2),
                  index=['a','c','b'],
                  columns=['one','two'])

df2 = pd.DataFrame(5 + np.arange(4).reshape(2,2),
                  index=['a','b'],
                  columns=['three','four'])

display(df1)
display(df2)

# 열방향으로 데이터추가 (같은 index 끼리는 같은 index 사용 - 별도로 만들지 않음)
result = pd.concat([df1, df2], axis=1, sort=True)
display(result)
#   one two three four
# a  0   1   5.0  6.0
# b  4   5   7.0  8.0
# c  2   3   NaN  NaN

# sort = False : column 정렬
# sort = True : row 정렬
# ignore_index : True - 설정된 인덱스를 무시하고 숫자 인덱스로 설정
#                False - 설정된 인덱스 사용
result = pd.concat([df1, df2], axis=0, ignore_index=True, sort=False)
#    one  two  three  four
# 0  0.0  1.0   NaN   NaN
# 1  2.0  3.0   NaN   NaN
# 2  4.0  5.0   NaN   NaN
# 3  NaN  NaN   5.0   6.0
# 4  NaN  NaN   7.0   8.0

< 결측치 처리 >

일반적으로 NaN으로 표현되는 결측치가 데이터 있을 수 있다.

결측치를 처리하는 방법은 두 가지가 있다.

  • 결측치가 들어가 있는 데이터를 무조건 삭제
  • 결측치를 다른 값으로 대체해서 사용

데이터가 얼마되지 않으면 삭제하는게 좋지만, 일반적으로는 결측치를 다른 값으로 대체해서 사용해야 한다.

< 결측치 처리 예제 >

import numpy as np
import pandas as pd

np.random.seed(0)

# 0 이상 10 미만의 정수형 난수를 균등분포로 추출해서 6 x 4 DataFrame 생성
df = pd.DataFrame(np.random.randint(0,10,(6,4)))

# index : 날짜를 이용. 2020-01-01 부터 1 일씩 증가
df.index = pd.date_range('20200101', '20200106')
# column 또한 설정
df.columns = ['A', 'B', 'C', 'D']

# NaN 값을 포함하는 새로운 column 'E'를 추가
df['E'] = [7, np.nan, 4, np.nan, 2, np.nan]

# 1. 결측치 제거 - dropna() 함수 사용
# (how = 'any') : NaN 이 하나라도 행에 포함되어 있으면 행 삭제
# (how = 'all') : 행의 모든 열이 NaN 인 경우 행을 삭제
new_df = df.dropna(how='any', inplace=False)
display(new_df)

# 2. 결측치를 다른 값으로 대체 - fillna() 함수 사용
# (value = 변경할 값) 형태로 전달
new_df = df.fillna(value=0)
display(new_df)

# isnull() : 값이 NaN인 값들에 대해서 True인 boolean mask 생성
my_mask = df['E'].isnull()  # boolean mask
display(my_mask)
# 이 boolean mask를 활용해서 boolean indexing 가능
# 값이 NaN인 항목을 추출할 수 있다.
display(df.loc[my_mask,:])

< 중복되는 행 처리 : duplicate >

import numpy as np
import pandas as pd

# ['one', 'one', 'one', 'two', 'two', 'two', 'two' ]
df = pd.DataFrame({'k1' : ['one'] * 3 + ['two'] * 4,
                  'k2' : [1,1,2,3,3,4,4]})
                  
# duplicated() : 중복행에 대한 boolean mask 추출
print(df.duplicated())
# 0    False
# 1     True
# 2    False
# 3    False
# 4     True
# 5    False
# 6     True
# dtype: bool

# drop_duplicates() : 중복행을 찾아서 지우고, 결과 DataFrame을 return
display(df.drop_duplicates())
#    k1   k2
# 0  one  1
# 2  one  2 
# 3  two  3
# 5  two  4

###############################################################

df = pd.DataFrame({'k1' : ['one'] * 3 + ['two'] * 4,
                  'k2' : [1,1,2,3,3,4,4],
                  'k3' : np.arange(7)})

# 인자로 column list를 전달해서 해당 column 쌍에 대한 중복확인 가능
display(df.drop_duplicates(['k1', 'k2']))
#    k1   k2  k3
# 0  one  1   0
# 2  one  2   2
# 3  two  3   3
# 5  two  4   5

< 값의 교체 : replace >

import numpy as np
import pandas as pd

np.random.seed(100)
df = pd.DataFrame(np.random.randint(0,10,(6,4)),
                 columns=['A','B','C','D'])

df['E'] = [7, np.nan, 4, np.nan, 2, np.nan]
display(df)

result = df.replace(8, -100)  # 값이 8인 것을 -100으로 전부 치환
display(result)
#      A     B    C    D    E
# 0  -100  -100   3    7   7.0
# 1    7     0    4    2   NaN
# 2    5     2    2    2   4.0
# 3    1     0  -100   4   NaN
# 4    0     9    6    2   2.0
# 5    4     1    5    3   NaN

< Grouping >

import numpy as np
import pandas as pd

my_dict = { '학과' : ['컴퓨터','체육교육과','컴퓨터','체육교육과','컴퓨터'],
            '학년' : [1, 2, 3, 2, 3],
            '이름' : ['홍길동', '김연아', '강감찬', '이순신', '신사임당'],
            '학점' : [1.5, 4.4, 3.7, 4.5, 3.8]}
            
df = pd.DataFrame(my_dict)

# 1. Series에 대한 Grouping
# 학점 값을 추출해서 학과를 기준으로 Grouping
# ex) 컴퓨터  : [1.5, 3.7, 3.8]
#     체육교육과 : [4.4, 4.5]
dept = df['학점'].groupby(df['학과'])

# display() 함수로 출력할 수 없다!!
display(deft)
# 결과 : <pandas.core.groupby.generic.SeriesGroupBy object at 0x0000014CCC9EDB08>

# group 내의 데이터를 확인하고 싶은경우 get_group() 함수 사용
print(dept.get_group('컴퓨터'))  # Series
# 0    1.5
# 2    3.7
# 4    3.8
# Name: 학점, dtype: float64

# 각 group의 요소수를 확인하고 싶은 경우 size() 함수 사용
print(dept.size())  # Series 형태로 return
# 학과
# 체육교육과    2
# 컴퓨터      3
# Name: 학점, dtype: int64

# mean() : 각 그룹별 평균값을 계산하여 Series로 return
print(dept.mean())
# 학과
# 체육교육과    4.45
# 컴퓨터      3.00
# Name: 학점, dtype: float64

# 2단계 grouping - (학과, 학년)을 기준으로 grouping
# Series와 DataFrame은 index와 column에 multi index 개념을 지원함
dept_year = df['학점'].groupby([df['학년'], df['학과']])
print(dept_year.mean())
# 학년  학과   
# 1   컴퓨터      1.50
# 2   체육교육과    4.45
# 3   컴퓨터      3.75
# Name: 학점, dtype: float64

# unstack() : 최하위 index를 column으로 설정 => 차원을 늘림
display(dept_year.mean().unstack())
# 학과   체육교육과   컴퓨터
# 학년
#   1       NaN      1.50
#   2      4.45      NaN
#   3       NaN      3.75

# 2. DataFrame에 대한 Grouping
df_group_dept = df.groupby(df['학과'])
display(df_group_dept.get_group('컴퓨터'))  # DataFrame
#     학과   학년   이름     학점
# 0  컴퓨터   1    홍길동    1.5
# 2  컴퓨터   3    강감찬    3.7
# 4  컴퓨터   3    신사임당  3.8

# 학과별 각 column의 평균
display(df_group_dept.mean())
#                학년      학점
# 학과
# 체육교육과    2.000000    4.45
# 컴퓨터       2.333333    3.00

df_dept_year = df['학점'].groupby([df['학과'], df['학년']])  # DataFrame
#                    학점
# 학과        학년	
# 체육교육과    2     4.45
# 컴퓨터        1     1.50
#              3     3.75
 
display(df_dept_year.mean().unstack())
#               학점
# 학년           1      2       3
#       학과			
# 체육교육과    NaN    4.45    NaN
#     컴퓨터    1.5    NaN     3.75

# 간단한 연습
# 1. 학과별 평균 학점은?
print(df['학점'].groupby(df['학과']).mean())  # Series
# 학과
# 체육교육과    4.45
# 컴퓨터      3.00
# Name: 학점, dtype: float64

# 2. 학과별 몇명이 존재하는가?
print(df.groupby(df['학과']).size())  # Series
# 학과
# 체육교육과    2
# 컴퓨터      3
# dtype: int64

< Group에 대한 for loop >

import numpy as np
import pandas as pd

my_dict = { '학과' : ['컴퓨터','체육교육과','컴퓨터','체육교육과','컴퓨터'],
            '학년' : [1, 2, 3, 2, 3],
            '이름' : ['홍길동', '김연아', '강감찬', '이순신', '신사임당'],
            '학점' : [1.5, 4.4, 3.7, 4.5, 3.8]}

df = pd.DataFrame(my_dict)

# 학과를 기준으로 Grouping한 후 for loop를 이용해서 반복처리 해보자
# 각 학과에 대해 ('학과명', '해당 학과의 DataFrame')의 tuple 형태로 가져옴
for (dept, group) in df.groupby(df['학과']):
    print(dept)
    display(group)
    
# 두 가지 이상의 data를 가지고 Grouping 했다면, tuple 형태로 가져옴
# DataFrame에 대해서 Grouping 할 때는 list 형태로 column 명만 전달해주어도 된다.
# 물론 [df['학과'],df['학년']] 형태로 전달해도 정상 동작함
for ((dept, year), group) in df.groupby(['학과','학년']):
    print(dept)
    print(year)
    display(group)