아이티윌_데이터 분석 55기/강의내용 필기_Python

#6 6일차_pd의 행·열 결합 / 수학 및 통계 함수·메서드 / 결측치 처리

ecosso 2026. 5. 18. 16:16

01 Pandas의 행 / 열 결합

 

02 수학 / 통계 함수 및 메서드

 -01 기본함수

 -02 math 함수

 -03 numpy 함수 / 메서드

 -04 pandas 함수 / 메서드

 

03 결측치

 -01 표현방법

 -02 확인

 -03 수정

 -04 결측치의 처리


01 Pandas의 행 / 열 결합

import pandas as pd
from pandas import Series, DataFrame

pd.concat(objs,                             # 결합대상 (리스트로 묶어서 전달)
                  axis = 0,                       # 결합방향 (0 : 세로방향, 1 : 가로방향)
                  join = 'outer',                # 결합방식 (기본은 모두 출력, inner : 공통키만 출력)
                  ignore_index = False) # 결합 후 인덱스 재배치 여부

 

하나의 행을 추가해야할 시, 여러 행을 추가해야할 시 pd.concat을 사용한다.

concat은 양방향 모두 결합이 가능하나 Series의 경우 DataFrame으로의 변환이 필요하다.

 

ex) 행/열 결합
pd.concat([DataFrame(arr3), DataFrame(arr4)])

pd.concat([DataFrame(arr3), DataFrame(arr4)])
Out[40]: 
    0   1   2   3
0   1   2   3   4
1   5   6   7   8
0  10  20  30  40
1  50  60  70  80

 

또한 index가 그대로 따라오는 문제가 있기 때문에 ignore_index = True 를 통해 index 를 재지정해준다.
pd.concat([DataFrame(arr3), DataFrame(arr4)], axis = 1, ignore_index=True)

pd.concat([DataFrame(arr3), DataFrame(arr4)], ignore_index=True)
Out[41]: 
    0   1   2   3
0   1   2   3   4
1   5   6   7   8
2  10  20  30  40
3  50  60  70  80

 

pd.concat([DataFrame(arr3), DataFrame(arr4)], axis = 1, ignore_index=True)

pd.concat([DataFrame(arr3), DataFrame(arr4)], axis = 1, ignore_index=True)
Out[44]: 
   0  1  2  3   4   5   6   7
0  1  2  3  4  10  20  30  40
1  5  6  7  8  50  60  70  80

 


 

ex) 데이터프레임 결합 (행 결합)
df1 = DataFrame({'name' : ['smith','allen'], 'sal':[1000,2000]})
df2 = DataFrame({'name' : ['king','ford'], 'sal':[3000,4000]})
df12 = pd.concat([df1,df2], ignore_index=True)

df12
Out[59]: 
    name   sal
0  smith  1000
1  allen  2000
2   king  3000
3   ford  4000


ex) 데이터프레임 결합 (컬럼 결합)
df3 = DataFrame({'name':['smith','allen'], 'sal':[1000,2000]})
df4 = DataFrame({'name':['smith','allen'], 'deptno':[10,20]]})
df34 = pd.concat([df3,df4], axis=1)

df34
Out[67]: 
    name   sal   name  deptno
0  smith  1000  smith      10
1  allen  2000  allen      20


ex) 서로 다른 키를 갖는 데이터프레임 결합
df5 = DataFrame({'name':['smith','allen'], 'sal':[1000,2000]})
df6 = DataFrame({'name':['ford','scott'], 'deptno':[10,20]})
pd.concat([df5, df6])                   # 서로 다른 키 컬럼도 모두 출력
pd.concat([df5, df6], join = 'outer')   # 위와 결과 동일
pd.concat([df5, df6], join = 'inner')   # 양쪽 데이터프레임의 같은 키만 출력됨

pd.concat([df5, df6])                   # 서로 다른 키 컬럼도 모두 출력
Out[71]: 
    name     sal  deptno
0  smith  1000.0     NaN
1  allen  2000.0     NaN
0   ford     NaN    10.0
1  scott     NaN    20.0

pd.concat([df5, df6], join = 'outer')   # 위와 결과 동일
Out[72]: 
    name     sal  deptno
0  smith  1000.0     NaN
1  allen  2000.0     NaN
0   ford     NaN    10.0
1  scott     NaN    20.0

pd.concat([df5, df6], join = 'inner')   # 양쪽 데이터프레임의 같은 키만 출력됨
Out[73]: 
    name
0  smith
1  allen
0   ford
1  scott

 


 

[연습문제]
emp_1.xlsx, emp_2.xlsx, emp_3.xlsx 파일 모두 합치기
emp1 = pd.read_excel('emp_1.xlsx')
emp2 = pd.read_excel('emp_2.xlsx')
emp3 = pd.read_excel('emp_3.xlsx')

emp1.head()
Out[120]: 
   EMPNO   ENAME       JOB
0   7369   SMITH     CLERK
1   7499   ALLEN  SALESMAN
2   7521    WARD  SALESMAN
3   7566   JONES   MANAGER
4   7654  MARTIN  SALESMAN

emp2.head()
Out[121]: 
   EMPNO   SAL    COMM  DEPTNO
0   7369   800     NaN      20
1   7499  1600   300.0      30
2   7521  1250   500.0      30
3   7566  2975     NaN      20
4   7654  1250  1400.0      30

emp3.head()
Out[122]: 
   EMPNO ENAME      JOB   SAL   COMM  DEPTNO
0   9999  PARK    CLERK  5000  100.0      10
1   9998   KIM  MANAGER  4800   20.0      20
2   9997   YOO  ANALYST  6000  500.0      30
3   9996  JUNG    CLERK  5100    NaN      10
4   9995  HYUN  MANAGER  4900    NaN      20

 

# index를 기준으로 결합하기 때문에 같은 키를 가지고 있는 경우 테이블간 index 순서가 다를 때 conat보다는 merge를 사용하는 것이 좋을 수도 있다.
 (예를 들어 0번 index에 a테이블은 'smith', b테이블은 'miller'라면 smith와 miller의 정보가 결합된다.)

pd.concat([emp1, emp2], axis = 1)               # 단순 컬럼 결합 (empno 컬럼 중복, 각 데이터프레임 위치 상관없이 결합)

pd.concat([emp1, emp2], axis = 1)               # 단순 컬럼 결합 (empno 컬럼 중복, 각 데이터프레임 위치 상관없이 결합)
Out[126]: 
    EMPNO   ENAME        JOB  EMPNO   SAL    COMM  DEPTNO
0    7369   SMITH      CLERK   7369   800     NaN      20
1    7499   ALLEN   SALESMAN   7499  1600   300.0      30
2    7521    WARD   SALESMAN   7521  1250   500.0      30
3    7566   JONES    MANAGER   7566  2975     NaN      20
4    7654  MARTIN   SALESMAN   7654  1250  1400.0      30
5    7698   BLAKE    MANAGER   7698  2850     NaN      30
6    7782   CLARK    MANAGER   7782  2450     NaN      10
7    7788   SCOTT    ANALYST   7788  3000     NaN      20
8    7839    KING  PRESIDENT   7839  5000     NaN      10
9    7844  TURNER   SALESMAN   7844  1500     0.0      30
10   7876   ADAMS      CLERK   7876  1100     NaN      20
11   7900   JAMES      CLERK   7900   950     NaN      30
12   7902    FORD    ANALYST   7902  3000     NaN      20
13   7934  MILLER      CLERK   7934  1300     NaN      10


emp12 = pd.merge(emp1, emp2)                    # empno 기준으로 결합됨

emp12
Out[128]: 
    EMPNO   ENAME        JOB   SAL    COMM  DEPTNO
0    7369   SMITH      CLERK   800     NaN      20
1    7499   ALLEN   SALESMAN  1600   300.0      30
2    7521    WARD   SALESMAN  1250   500.0      30
3    7566   JONES    MANAGER  2975     NaN      20
4    7654  MARTIN   SALESMAN  1250  1400.0      30
5    7698   BLAKE    MANAGER  2850     NaN      30
6    7782   CLARK    MANAGER  2450     NaN      10
7    7788   SCOTT    ANALYST  3000     NaN      20
8    7839    KING  PRESIDENT  5000     NaN      10
9    7844  TURNER   SALESMAN  1500     0.0      30
10   7876   ADAMS      CLERK  1100     NaN      20
11   7900   JAMES      CLERK   950     NaN      30
12   7902    FORD    ANALYST  3000     NaN      20
13   7934  MILLER      CLERK  1300     NaN      10


pd.concat([emp12, emp3], ignore_index=True)        # 행 결합

pd.concat([emp12, emp3], ignore_index=True)
Out[129]: 
    EMPNO   ENAME        JOB   SAL    COMM  DEPTNO
0    7369   SMITH      CLERK   800     NaN      20
1    7499   ALLEN   SALESMAN  1600   300.0      30
2    7521    WARD   SALESMAN  1250   500.0      30
3    7566   JONES    MANAGER  2975     NaN      20
4    7654  MARTIN   SALESMAN  1250  1400.0      30
5    7698   BLAKE    MANAGER  2850     NaN      30
6    7782   CLARK    MANAGER  2450     NaN      10
7    7788   SCOTT    ANALYST  3000     NaN      20
8    7839    KING  PRESIDENT  5000     NaN      10
9    7844  TURNER   SALESMAN  1500     0.0      30
10   7876   ADAMS      CLERK  1100     NaN      20
11   7900   JAMES      CLERK   950     NaN      30
12   7902    FORD    ANALYST  3000     NaN      20
13   7934  MILLER      CLERK  1300     NaN      10
14   9999    PARK      CLERK  5000   100.0      10
15   9998     KIM    MANAGER  4800    20.0      20
16   9997     YOO    ANALYST  6000   500.0      30
17   9996    JUNG      CLERK  5100     NaN      10
18   9995    HYUN    MANAGER  4900     NaN      20
19   9994    CHOI    ANALYST  6100     NaN      30

02 수학 / 통계 함수 및 메서드

 -01 기본함수
dir(__builtins__)

abs() # 절댓값
round() # 반올림
min() # 최소
max() # 최대
sum() # 더하기
pow() # 거듭제곱
any() # 참 여부 (이 중에 참이 있는가)
all() # 참 여부 (모두 참인가)

 


 -02 math 함수
import math
dir(math)

math.trunc()
math.ceil()
math.floor()
math.sqrt()
math.log()


 -03 numpy 함수 / 메서드
import numpy as np
arr1 = np.array([1,2,3,4,5])

dir(np)     # numpy 함수목록
dir(arr1)   # numpy 객체 호출 가능한 메서드 목록

np.sqrt(arr1)
np.exp()
np.log()
np.log10()
np.log1p()

 

※ 로그 스케일링(Log Scaling)은 회귀분석에서 종속변수(y)나 독립변수(x)가 한쪽으로 심하게 치우친 분포를 가질 때 자주 사용하는 데이터 변환 기법이다.

특히 값의 범위가 매우 크거나 이상치의 영향이 큰 경우, 로그 변환을 적용하면 데이터 분포를 보다 안정적으로 만들고 모델의 성능을 개선하는 데 도움이 된다.

다만 log() 함수는 입력값이 반드시 0보다 커야 하므로, 데이터에 0이 포함된 경우에는 일반적으로 다음과 같은 형태로 변환한다.

log(x + 1)
 

이 방식은 0 값을 유지하면서도 로그 변환의 효과를 적용할 수 있어 실무에서 많이 사용된다.
단, 로그 변환은 데이터에 음수 값이 없다는 가정하에서 적용해야 한다.

 

np.log1p()  # np.log(arr1+1)와 같음

 

np.abs()
np.round()
np.ceil()
np.floor()

np.sum()
np.mean()
np.median()     # 중앙값
np.var()           # 분산
np.std()           # 표준편차 
np.min()
np.max()
np.cumsum()     # 누적합
np.cumprod()     # 누적곱
np.percentile(data, 25) # Q1

 

# ** 메서드
arr1.sum()
arr1.mean()
arr1.var()


 -04 pandas 함수 / 메서드

import pandas as pd
from pandas import Series, DataFrame
s1 = Series(arr1)
s2 = Series([1,1,1,1,2,3])
df1 = DataFrame(np.arange(1,21).reshape(5,4), columns = list('ABCD'))

dir(pd)         # pandas 함수목록
dir(arr1)       # pandas 객체 호출 가능한 매서드 목록

s1.sum()
s1.var()
s1.std()
s1.median()
s2.mode().iloc[0]  # 다중최빈값을 갖는 경우 때문에 Series로 리턴되므로 iloc[0]을 써서 스칼라로 리턴

df1.corr()      # 각 컬럼별 피어슨 상관계수 출력

df1.corr()      # 각 컬럼별 피어슨 상관계수 출력
Out[170]: 
     A    B    C    D
A  1.0  1.0  1.0  1.0
B  1.0  1.0  1.0  1.0
C  1.0  1.0  1.0  1.0
D  1.0  1.0  1.0  1.0


df1.cov()       # 각 컬럼별 공분산 출력

df1.cov()       # 각 컬럼별 공분산 출력
Out[171]: 
      A     B     C     D
A  40.0  40.0  40.0  40.0
B  40.0  40.0  40.0  40.0
C  40.0  40.0  40.0  40.0
D  40.0  40.0  40.0  40.0


df1.describe()  # 수치컬럼 요약 정보 (수, 평균, 표준편차, 최솟값, 사분위수, 최댓값)

df1.describe()
Out[169]: 
               A          B          C          D
count   5.000000   5.000000   5.000000   5.000000
mean    9.000000  10.000000  11.000000  12.000000
std     6.324555   6.324555   6.324555   6.324555
min     1.000000   2.000000   3.000000   4.000000
25%     5.000000   6.000000   7.000000   8.000000
50%     9.000000  10.000000  11.000000  12.000000
75%    13.000000  14.000000  15.000000  16.000000
max    17.000000  18.000000  19.000000  20.000000

 

** numpy / pandas 수학통계 함수 비교
np.nan          # 결측치 표현방법

a1 = np.array([1,2,3,4,5])
a2 = np.array([1,2,np.nan,4,5])
s1 = Series(a1)
s2 = Series(a2)

 1) 분산결과
a1.var()   # 2.0
np.var(a1) # 2.0
s1.var()   # 2.5

numpy의 var와 pandas의 var가 다르게 출력된다.
numpy의 경우 옵션을 일부 조정해야 할 필요가 있어 pandas를 대체로 많이 사용한다.

# ** 분산 계산 : sum((값-평균)^2/n-1)
전체 샘플의 갯수로 나누어준다. (numpy)
pandas의 경우 통계분산이기 때문에 n-1로 나누어준다.

sum((a1 - a1.mean())**2)/len(a1)          # numpy var 계산 방식
sum((a1 - a1.mean())**2)/(len(a1)-1)    # pandas var 계산 방식

sum((a1 - a1.mean())**2)/len(a1)        # numpy var 계산 방식
Out[185]: np.float64(2.0)

sum((a1 - a1.mean())**2)/(len(a1)-1)    # pandas var 계산 방식
Out[186]: np.float64(2.5)


+) np.var?

기본적으로 자유도에 대한 옵션(ddof)이 0으로 설정되어 있음을 확인할 수 있다.

Signature:      
np.var(
    a,
    axis=None,
    dtype=None,
    out=None,
    ddof=0,
    keepdims=<no value>,
    *,
    where=<no value>,
    mean=<no value>,
    correction=<no value>,
)

 

따라서 ddof = 1로 설정하면 pandas의 분산과 같은 결과가 나온다.

np.var(a1, ddof = 1)

np.var(a1, ddof = 1)
Out[188]: np.float64(2.5)

 

+) s1.var?

Signature:
s1.var(
    axis: 'Axis | None' = None,
    skipna: 'bool' = True,
    ddof: 'int' = 1,
    numeric_only: 'bool' = False,
    **kwargs,
)
Docstring:
Return unbiased variance over requested axis.

Normalized by N-1 by default. This can be changed using the ddof argument.

 

따라서 ddof = 0으로 설정하면 numpy의 분산과 같은 결과가 나온다.

s1.var(ddof = 0)

s1.var(ddof = 0)
Out[193]: 2.0

 

 2) NA 무시 옵션
a2.sum()  # NA로 리턴 (skipna 옵션이 없어서 NA를 포함한 객체의 연산 결과는 항상 NA로 리턴)
s2.sum()  # 12 (skipna = True가 기본값이므로 NA를 무시한 결과가 리턴)

a2.sum()
Out[194]: np.float64(nan)

s2.sum()
Out[195]: np.float64(12.0)

 

s2.sum?

Signature:
s2.sum(
    axis: 'Axis | None' = None,
    skipna: 'bool' = True,
    numeric_only: 'bool' = False,
    min_count: 'int' = 0,
    **kwargs,
)
Docstring:
Return the sum of the values over the requested axis.

This is equivalent to the method ``numpy.sum``.

 

numpy의 경우 skipna 기능이 없어 산술연산 시 na가 존재하는 경우 이에 대한 전처리가 필요하다.

pandas의 경우 skipna 기능이 있으며 True가 기본값으로 설정되어 있기 때문에 산술연산 시 자동으로 NA를 제외한 결과가 리턴된다.


03 결측치

 -01 표현방법
np.nan  # 파이썬에서는 np.nan이 기본적으로 float type이다. 

from numpy import nan as NA


 -02 확인
np.isnan([1,2,3,np.nan])    # numpy용 함수
a2.isnan()                          # 메서드가 존재하지 않는다.

 

a3 = Series(['a','b','d',NA])  # 03-01의 NA 선언 필수

np.isnan(a3) # error (np.isnan 함수는 NA가 str 타입일 때는 체크 불가)

np.isnan(a3) # error(np.isnan 함수는 NA가 str 타입일 때는 체크 불가)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[271], line 1
----> 1 np.isnan(a3) # error(np.isnan 함수는 NA가 str 타입일 때는 체크 불가)

File ~\anaconda3\Lib\site-packages\pandas\core\generic.py:2193, in NDFrame.__array_ufunc__(self, ufunc, method, *inputs, **kwargs)
   2189 @final
   2190 def __array_ufunc__(
   2191     self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any
   2192 ):
-> 2193     return arraylike.array_ufunc(self, ufunc, method, *inputs, **kwargs)

File ~\anaconda3\Lib\site-packages\pandas\core\arraylike.py:399, in array_ufunc(self, ufunc, method, *inputs, **kwargs)
    396 elif self.ndim == 1:
    397     # ufunc(series, ...)
    398     inputs = tuple(extract_array(x, extract_numpy=True) for x in inputs)
--> 399     result = getattr(ufunc, method)(*inputs, **kwargs)
    400 else:
    401     # ufunc(dataframe)
    402     if method == "__call__" and not kwargs:
    403         # for np.<ufunc>(..) calls
    404         # kwargs cannot necessarily be handled block-by-block, so only
    405         # take this path if there are no kwargs

TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

 

pd.isnull([1,2,3,np.nan])   # pandas용 함수 (상수의 NA 여부 확인 시에는 메서드가 아닌 함수를 사용해야 함)
s2.isnull()                         # 메서드가 존재한다. (데이터 전체의 NA 여부 확인 시에 메서드 호출)

pd.isnull(a3)  # 문자형 NA 체크 가능

pd.isnull(a3)  # 문자형 NA 체크 가능
Out[272]: 
0    False
1    False
2    False
3     True
dtype: bool

 

 -03 수정

 1) 직접수정
s2[s2.isnull()] = 10

 

 2) np.where

s2.isnull 자체가 NA인지 아닌지를 출력하기 때문에 아래와 같은 형식의 코드를 작성할 필요가 없다.

np.where(s2.isnull() == np.nan)

 

s3 = Series([1,2,3,4,np.nan])
s3 = Series(np.where(s3.isnull(), 10, s3))

s3
Out[251]: 
0     1.0
1     2.0
2     3.0
3     4.0
4    10.0
dtype: float64

 

 3) NA 치환 메서드

s3.fillna?

Signature:
s3.fillna(
    value: 'Hashable | Mapping | Series | DataFrame | None' = None,
    *,
    method: 'FillnaOptions | None' = None,
    axis: 'Axis | None' = None,
    inplace: 'bool_t' = False,
    limit: 'int | None' = None,
    downcast: 'dict | None | lib.NoDefault' = <no_default>,
) -> 'Self | None'
Docstring:
Fill NA/NaN values using the specified method.

 

s3 = Series([1,2,3,4,np.nan])
s3.fillna(10, inplace = True)  # 원본 테이블에 바로 반영된다.

s3
Out[255]: 
0     1.0
1     2.0
2     3.0
3     4.0
4    10.0
dtype: float64


ex) emp에서
 1) 각 컬럼별 결측치 수 확인
 2) COMM 컬럼의 결측치를 중앙값으로 대치 후 평균을 소숫점 둘째자리까지 반올림하여 출력

 

emp = pd.read_csv('emp.csv')
emp.head()

 

# python은 결측치 자체가 type을 가진다. (np.nan이 기본적으로 float type)
# 따라서 emp에서 MGR, COMM이 원래 데이터는 정수형이었음에도 불구하고 NaN이 포함되어 있었기 때문에
# float type으로 바뀌어서 조회되었다.

emp.head()
Out[259]: 
   EMPNO   ENAME       JOB     MGR         HIREDATE   SAL    COMM  DEPTNO
0   7369   SMITH     CLERK  7902.0  1980-12-17 0:00   800     NaN      20
1   7499   ALLEN  SALESMAN  7698.0  1981-02-20 0:00  1600   300.0      30
2   7521    WARD  SALESMAN  7698.0  1982-02-22 0:00  1250   500.0      30
3   7566   JONES   MANAGER  7839.0  1981-04-02 0:00  2975     NaN      20
4   7654  MARTIN  SALESMAN  7698.0  1981-09-28 0:00  1250  1400.0      30




 1) 컬럼별 결측치 확인
# Series에도 DataFrame에도 모두 적용 가능하다.
pd.isnull(emp).sum()

pd.isnull(emp).sum()
Out[241]: 
EMPNO       0
ENAME       0
JOB         0
MGR         0
HIREDATE    0
SAL         0
COMM        0
DEPTNO      0
dtype: int64

 

 2) COMM 컬럼 결측치 대치 후 반올림하여 출력
# R에서 실습했던 것처럼 코드를 작성해도 대치는 가능하나 색인을 2번 진행하였기 때문에 warning이 발생한다.
emp['COMM'][emp['COMM'].isnull()] = 10

 

'copy of a slice from a DataFrame'

원본 데이터에서 슬라이싱한 데이터를 대치하는 것을 Python에서는 차후 막을 것으로 예쌍된다.

emp['COMM'][emp['COMM'].isnull()] = 10
C:\Users\itwill\AppData\Local\Temp\ipykernel_12396\3095320992.py:1: FutureWarning: ChainedAssignmentError: behaviour will change in pandas 3.0!
You are setting values through chained assignment. Currently this works in certain cases, but when using Copy-on-Write (which will become the default behaviour in pandas 3.0) this will never work to update the original DataFrame or Series, because the intermediate object on which we are setting values will behave as a copy.
A typical example is when you are setting values in a column of a DataFrame, like:

df["col"][row_indexer] = value

Use `df.loc[row_indexer, "col"] = values` instead, to perform the assignment in a single step and ensure this keeps updating the original `df`.

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

  emp['COMM'][emp['COMM'].isnull()] = 10
C:\Users\itwill\AppData\Local\Temp\ipykernel_12396\3095320992.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  emp['COMM'][emp['COMM'].isnull()] = 10

 

# 인덱싱은 한 번만 진행하고 대치하는 것이 좋다.
emp.loc[emp['COMM'].isnull(), 'COMM'] = emp['COMM'].median()
emp['COMM'].mean().round(2)

emp['COMM'].mean().round(2)
Out[246]: np.float64(442.86)

 -04 결측치의 처리

 1) 삭제

 - NA를 포함한 행 삭제

 - NA가 n개 이상 포함된 행, 컬럼 삭제


df = DataFrame([[NA,1,2,3,4],
                [NA,NA,3,4,5],
                [NA,NA,NA,4,5],
                [NA,NA,NA,NA,5],
                [NA,NA,NA,NA,NA],
                [1,2,3,4,5]], columns=list('ABCDE'))

df
Out[275]: 
     A    B    C    D    E
0  NaN  1.0  2.0  3.0  4.0
1  NaN  NaN  3.0  4.0  5.0
2  NaN  NaN  NaN  4.0  5.0
3  NaN  NaN  NaN  NaN  5.0
4  NaN  NaN  NaN  NaN  NaN
5  1.0  2.0  3.0  4.0  5.0

 

df.dropna?

Signature:
df.dropna(
    *,
    axis: 'Axis' = 0,
    how: 'AnyAll | lib.NoDefault' = <no_default>,
    thresh: 'int | lib.NoDefault' = <no_default>,
    subset: 'IndexLabel | None' = None,
    inplace: 'bool' = False,
    ignore_index: 'bool' = False,
) -> 'DataFrame | None'
Docstring:
Remove missing values.

ex) NA가 포함된 행 제거
df.dropna()  # 각 행마다 NA가 하나라도 포함되어 있으면 행을 제거

df.dropna()  # 각 행마다 NA가 하나라도 포함되어 있으면 행을 제거
Out[278]: 
     A    B    C    D    E
5  1.0  2.0  3.0  4.0  5.0


df.dropna(how='all') # 행의 모든 값이 NA인 행만 제거

df.dropna(how='all')
Out[279]: 
     A    B    C    D    E
0  NaN  1.0  2.0  3.0  4.0
1  NaN  NaN  3.0  4.0  5.0
2  NaN  NaN  NaN  4.0  5.0
3  NaN  NaN  NaN  NaN  5.0
5  1.0  2.0  3.0  4.0  5.0

 

df.dropna(subset=['E']) # E라는 컬럼에 NA값이 하나라도 포함된다면 행을 제거

df.dropna(subset=['E'])
Out[280]: 
     A    B    C    D    E
0  NaN  1.0  2.0  3.0  4.0
1  NaN  NaN  3.0  4.0  5.0
2  NaN  NaN  NaN  4.0  5.0
3  NaN  NaN  NaN  NaN  5.0
5  1.0  2.0  3.0  4.0  5.0

ex) NA가 포함된 컬럼 제거
df.dropna(axis=1) # 모든 컬럼이 삭제되었음

df.dropna(axis=1)
Out[281]: 
Empty DataFrame
Columns: []
Index: [0, 1, 2, 3, 4, 5]

ex) 횟수 기반 삭제
df.dropna(thresh=4) # 각 행별로(axis=0) NA가 아닌 값의 수가 4 미만일 때 삭제 (4 이상인 경우만 남음)

NA 개수가 4개인 것이 아니라 NA가 아닌 값의 개수가 기준이다.

df.dropna(thresh=4)
Out[283]: 
     A    B    C    D    E
0  NaN  1.0  2.0  3.0  4.0
5  1.0  2.0  3.0  4.0  5.0

 

df.isnull().sum(axis=1) # 행별로 NA 개수 구하기

df.isnull().sum(axis=1)
Out[284]: 
0    1
1    2
2    3
3    4
4    5
5    0
dtype: int64

 

~(df.isnull().sum(axis=1) > 4) # NOT 연산자는 ~
df.loc[~(df.isnull().sum(axis=1) >= 4), :] # 각 행별로(axis=0) NA가 4회 이상 포함된 행 삭제

df.loc[~(df.isnull().sum(axis=1) >= 4), :]
Out[288]: 
     A    B    C    D    E
0  NaN  1.0  2.0  3.0  4.0
1  NaN  NaN  3.0  4.0  5.0
2  NaN  NaN  NaN  4.0  5.0
5  1.0  2.0  3.0  4.0  5.0

ex) NA가 5회 이상 포함된 컬럼 삭제
df.loc[:,~(df.isnull().sum(axis=0) >= 5)]

df.loc[:,~(df.isnull().sum(axis=0) >= 5)]
Out[300]: 
     B    C    D    E
0  1.0  2.0  3.0  4.0
1  NaN  3.0  4.0  5.0
2  NaN  NaN  4.0  5.0
3  NaN  NaN  NaN  5.0
4  NaN  NaN  NaN  NaN
5  2.0  3.0  4.0  5.0

 2) 대치

df.fillna(value,               # 대체값 (상수, 딕셔너리, Series, DataFrame)
            method =,         # {'backfill', 'bfill', 'ffill'}
            axis = 0)           # 대치방향

 

df = DataFrame({'A':[10,20,30,NA], 'B':[NA,200,300,400], 'C':[1,2,3,4]})

df
Out[302]: 
      A      B  C
0  10.0    NaN  1
1  20.0  200.0  2
2  30.0  300.0  3
3   NaN  400.0  4

 


ex) 상수로 대치
df.fillna(100)

df.fillna(100)
Out[303]: 
       A      B  C
0   10.0  100.0  1
1   20.0  200.0  2
2   30.0  300.0  3
3  100.0  400.0  4

 

ex) 각 컬럼별로 서로 다른 값으로 대치
df.fillna({'A':40,'B':100}) # 각 컬럼에 어울리는 값을 전달한다.

df.fillna({'A':40,'B':100})
Out[305]: 
      A      B  C
0  10.0  100.0  1
1  20.0  200.0  2
2  30.0  300.0  3
3  40.0  400.0  4

ex) 이전값 / 이후값 대치
df['A'].fillna(method='ffill')    # 이전값으로 대치
df['A'].ffill()      # 이전값으로 대치
df['B'].bfill()     # 이후값으로 대치

df['A'].fillna(method='ffill') # 이전값으로 대치
C:\Users\itwill\AppData\Local\Temp\ipykernel_12396\4026065074.py:1: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
  df['A'].fillna(method='ffill') # 이전값으로 대치
Out[310]: 
0    10.0
1    20.0
2    30.0
3    30.0
Name: A, dtype: float64

df['A'].ffill()     # 이전값으로 대치
Out[311]: 
0    10.0
1    20.0
2    30.0
3    30.0
Name: A, dtype: float64

df['B'].bfill()     # 이후값으로 대치
Out[312]: 
0    200.0
1    200.0
2    300.0
3    400.0
Name: B, dtype: float64

 

[ 연습문제 ]
sub = pd.read_csv('subway2.csv', encoding='cp949', skiprows = 1)
sub.head()

 (1) 역이름 모두 채우기

sub['전체'] = sub['전체'].ffill()

sub
Out[349]: 
         전체  구분  05~06  06~07   07~08  ...   20~21   21~22   22~23  23~24  24~01
0    서울역(1)  승차  17465  18434   50313  ...  108909  116350   88902  49049   4558
1    서울역(1)  하차   7829  48553  110250  ...   65388   59285   50266  32182  13943
2    시 청(1)  승차   2993   4473    7633  ...   74034   85680   69327  24653   1691
3    시 청(1)  하차   4142  19730   67995  ...   25171   14515    8552   5349   1603
4       종 각  승차   7371   7836   14545  ...  138406  176800  182774  86306   6074
..      ...  ..    ...    ...     ...  ...     ...     ...     ...    ...    ...
227     총신대  하차   1094  13095   26021  ...   63246   56352   57793  37546  10558
228   사당(4)  승차  12640  32077   90179  ...   44821   45749   48776  25147   3197
229   사당(4)  하차   2435  20684   39923  ...   46874   45369   47597  34915  16260
230     남태령  승차    388    878    2127  ...    1046     806     539    227     51
231     남태령  하차     77   1384    3142  ...    1374    1460    1515   1014    387

 

 (2) 역별, 승하차별 인원수 총합 구하기
sub.iloc[:,2:].sum(axis=1)      # Series로 출력 (1차원)
sub.iloc[:,2:].sum(axis=1).values.reshape(-1,1)                   # values로 값만 꺼내서 reshape을 토대로 2차원 구조를 만든다.
sub2 = DataFrame(sub.iloc[:,2:].sum(axis=1).values.reshape(-1,1),columns = ['총합'])   # DataFrame으로 출력 (★)
pd.concat([sub.iloc[:,:2], sub2], axis=1)

pd.concat([sub.iloc[:,:2], sub2], axis=1)
Out[360]: 
         전체  구분       총합
0    서울역(1)  승차  1830134
1    서울역(1)  하차  1565575
2    시 청(1)  승차   808764
3    시 청(1)  하차   835631
4       종 각  승차  1604404
..      ...  ..      ...
227     총신대  하차   902911
228   사당(4)  승차  1072854
229   사당(4)  하차   791552
230     남태령  승차    35418
231     남태령  하차    36025

[232 rows x 3 columns]

 

ex) 특정 Series의 값으로 대치 (★ 매우 중요)
df

다른 컬럼의 같은 위치에 있는 값으로 대치한다.

즉, A의 NA는 C의 4로 B의 NA는 C의 1로 대치된다.

df
Out[361]: 
      A      B  C
0  10.0    NaN  1
1  20.0  200.0  2
2  30.0  300.0  3
3   NaN  400.0  4

 

df['A'].fillna(df['C'])

df['A'].fillna(df['C'])
Out[362]: 
0    10.0
1    20.0
2    30.0
3     4.0
Name: A, dtype: float64

 

[ 연습문제 ]
df = pd.read_csv('boston_na.csv')

# 결측치 확인하기

 1) .info()

df.info()

506 entries이므로 non-null count가 506이 아닌 컬럼은 NA를 포함하고 있음을 뜻한다.

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 506 entries, 0 to 505
Data columns (total 14 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   crim     506 non-null    float64
 1   zn       506 non-null    float64
 2   indus    506 non-null    float64
 3   chas     506 non-null    int64  
 4   nox      506 non-null    float64
 5   rm       494 non-null    object 
 6   age      506 non-null    float64
 7   dis      501 non-null    object 
 8   rad      506 non-null    int64  
 9   tax      506 non-null    int64  
 10  ptratio  506 non-null    float64
 11  black    506 non-null    float64
 12  lstat    506 non-null    float64
 13  medv     506 non-null    float64
dtypes: float64(9), int64(3), object(2)
memory usage: 55.5+ KB

 

 2) .isnull().sum()

각 컬럼별 결측치 수를 확인한다.

df.isnull().sum()

df.isnull().sum()
Out[366]: 
crim        0
zn          0
indus       0
chas        0
nox         0
rm         12
age         0
dis         5
rad         0
tax         0
ptratio     0
black       0
lstat       0
medv        0
dtype: int64

 

 3) .isnull().any(axis=0)

각 컬럼별 결측치 포함 여부를 확인한다.

df.isnull().any(axis=0)

df.isnull().any(axis=0)
Out[367]: 
crim       False
zn         False
indus      False
chas       False
nox        False
rm          True
age        False
dis         True
rad        False
tax        False
ptratio    False
black      False
lstat      False
medv       False
dtype: bool

  2) 삭제

 

ex) 특정 Series의 값으로 대치
df['A'].fillna(df['C'])


[ 연습문제 ]
df = pd.read_csv('boston_na.csv')
df.info()
df.isnull().sum()
df.isnull().any(axis=0)
pd.set_option('display.max_columns', None)
df.head()

# 1) dis 컬럼에 대해 결측치를 전체 평균으로 대치 후 중앙값을 소수점 셋째자리까지 반올림하여 출력
df.loc[df['dis'].isin(['-', '.']), 'dis'] = np.nan
df['dis'] = df['dis'].astype('float')
df['dis'] = df['dis'].fillna(df['dis'].mean())

round(df['dis'].median(),3)

df.loc[df['dis'].isin(['-', '.']), 'dis'] = np.nan
df['dis'] = df['dis'].astype('float')
df['dis'] = df['dis'].fillna(df['dis'].mean())

round(df['dis'].median(),3)

Out[498]: 3.299


# 문제풀이
df['dis'].astype('float')                                  # '-' 때문에 변경 불가
df.loc[df['dis'] == '-', 'dis'] = NA                    # - -> NA로 변경
df['dis'].astype('float')                                  # '.' 때문에 변경 불가
df.loc[df['dis'] == '.', 'dis'] = NA                    # . -> NA로 변경
df['dis'] = df['dis'].astype('float')                   # 데이터타입 변환
df['dis'] = df['dis'].fillna(df['dis'].mean())      # 평균으로 대치
round(df['dis'].median(), 3)

df['dis'].astype('float')              # '-' 때문에 변경 불가
df.loc[df['dis'] == '-', 'dis'] = NA   # - -> NA로 변경
df['dis'].astype('float')              # '.' 때문에 변경 불가
df.loc[df['dis'] == '.', 'dis'] = NA   # . -> NA로 변경
df['dis'] = df['dis'].astype('float')  # 데이터타입 변환
df['dis'] = df['dis'].fillna(df['dis'].mean())     # 평균으로 대치
round(df['dis'].median(), 3)
Out[499]: 3.299

# 2) rm 컬럼에 대해 chas별 평균으로 결측치 대치 후 분산을 소숫점 셋째자리까지 반올림하여 출력

# 문제풀이

df['rm'].astype('float')                    # '     ' 때문에 변경 불가
df.loc[df['rm'] == '     ', : ] = NA      # 공백 제거 후 NA로 변경
df['rm'] = df['rm'].astype('float')     # 데이터타입 변환

rm_mean = df.groupby('chas')['rm'].transform('mean')        # chas별 평균 구하기
df['rm'] = df['rm'].fillna(rm_mean)
round(df['rm'].var(),3)