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

#8 8일차_빈도수 / 유일값 확인, 중복값 처리, shift, multi-level index

ecosso 2026. 5. 20. 16:21

01 빈도수 / 유일값 확인

 -01 빈도수 확인

 -02 유일값

 

02 중복값 처리 (unique / duplicated / drop_duplicates)

 -01 unique

 -02 duplicated

 

03 shift (데이터의 이동)

 -01 이전값 가져오기

 -02 이후값 가져오기

 -03 컬럼의 이동

 -04 그룹별 이동

 

04 multi - level index

 -01 생성

 -02 색인

 -03 연산

 

05 multi - level index의 처리

 -01 drop

 -02 sort_index

 -03 swaplevel


01 빈도수 / 유일값 확인

 -01 빈도수 확인

 1) groupby + count

 2) value_counts 메서드

emp.value_counts?

Signature:
emp.value_counts(
    subset: 'IndexLabel | None' = None,
    normalize: 'bool' = False,
    sort: 'bool' = True,
    ascending: 'bool' = False,
    dropna: 'bool' = True,
) -> 'Series'
Docstring:
Return a Series containing the frequency of each distinct row in the Dataframe.

 

emp.value_counts(subset,                         # 도수를 계산할 대상 선택
                               normalize = False,      # 비율 출력 여부
                               sort =  True,                # 정렬 여부
                               ascending = False,     # 빈도수가 큰 순서대로 출력됨
                               dropna = True)            # NA는 무시하고 도수 계산

 

emp.groupby('DEPTNO')['DEPTNO'].count()

emp.groupby('DEPTNO')['DEPTNO'].count()
Out[70]: 
DEPTNO
10    3
20    5
30    6
Name: DEPTNO, dtype: int64

 ▲ key에 대해 정렬을 진행한다.

 

emp['DEPTNO'].value_counts()

emp['DEPTNO'].value_counts()
Out[71]: 
DEPTNO
30    6
20    5
10    3
Name: count, dtype: int64

 ▲ DEPTNO 열에 대해 정렬을 진행한다.

 

emp['DEPTNO'].value_counts(normalize = True)

emp['DEPTNO'].value_counts(normalize = True)
Out[151]: 
DEPTNO
30    0.428571
20    0.357143
10    0.214286
Name: proportion, dtype: float64

 ▲ normalize = True 옵션을 통해 도수가 아닌 비율을 출력했다.

 

 +) .value_counts(normalize = True)로 클래스간 비율을 확인해야 하는 이유

 이진 분류일 때 7:3 정도의 불균형은 거의 당연하다고 여겨질 정도로 흔하게 발생한다.

 일반적으로 클래스 비율이 8:2를 초과하는 시점부터 클래스 불균형 문제를 본격적으로 고려해야 하며, 모델이 다수 클래스 중심으로 예측하는 경향이 강해질 가능성이 높아진다.

 

 특히 고객 이탈 예측, 사망률 예측과 같이 소수 클래스에 대한 탐지가 더 중요한 문제에서는 단순 정확도(Accuracy)만으로 모델 성능을 평가하기 어렵다.

 따라서 재현율(Recall), 정밀도(Precision), F1-Score와 같은 지표를 함께 고려하며, 필요에 따라 오버샘플링(Over Sampling), 언더샘플링(Under Sampling), 클래스 가중치(Class Weight) 조정 등의 기법을 적용해 클래스 불균형 문제를 완화해야 한다.


 -02 유일값

emp['JOB'].unique()

emp['JOB'].unique()
Out[72]: 
array(['CLERK', 'SALESMAN', 'MANAGER', 'ANALYST', 'PRESIDENT'],
      dtype=object)

 ▲ sql의 distinct처럼 자동으로 정렬되어 리턴된다.


02 중복값 처리 (unique / duplicated / drop_duplicates)

 -01 unique

Series에서만 호출이 가능하며 정렬을 자동으로 보장하지 않는다.

emp['JOB'].unique()

emp['JOB'].unique()
Out[73]: 
array(['CLERK', 'SALESMAN', 'MANAGER', 'ANALYST', 'PRESIDENT'],
      dtype=object)

 -02 duplicated

emp['JOB'].duplicated() # 중복 여부 확인

emp['JOB'].duplicated()
Out[74]: 
0     False
1     False
2      True
3     False
4      True
5      True
6      True
7     False
8     False
9      True
10     True
11     True
12     True
13     True
Name: JOB, dtype: bool

 

# 중복되지 않은 행만 출력 (중복행 제거)

emp.loc[~emp['JOB'].duplicated(), : ]

중복이 된 경우가 True이므로 '~'를 통해 중복되지 않은 고유한 값만 추출한다.

emp.loc[~emp['JOB'].duplicated(), :]
Out[76]: 
   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
3   7566  JONES    MANAGER  7839.0  1981-04-02 0:00  2975    NaN      20
7   7788  SCOTT    ANALYST  7566.0  1987-04-17 0:00  3000    NaN      20
8   7839   KING  PRESIDENT     NaN  1981-11-17 0:00  5000    NaN      10

 

# 중복된 행만 출력

emp.loc[emp['JOB'].duplicated(), :]

'~'를 제외하니 중복된 행만 출력됨을 확인할 수 있다.

emp.loc[emp['JOB'].duplicated(), :]
Out[77]: 
    EMPNO   ENAME       JOB     MGR         HIREDATE   SAL    COMM  DEPTNO
2    7521    WARD  SALESMAN  7698.0  1982-02-22 0:00  1250   500.0      30
4    7654  MARTIN  SALESMAN  7698.0  1981-09-28 0:00  1250  1400.0      30
5    7698   BLAKE   MANAGER  7839.0  1981-05-01 0:00  2850     NaN      30
6    7782   CLARK   MANAGER  7839.0  1981-06-09 0:00  2450     NaN      10
9    7844  TURNER  SALESMAN  7698.0  1981-09-08 0:00  1500     0.0      30
10   7876   ADAMS     CLERK  7788.0  1987-05-23 0:00  1100     NaN      20
11   7900   JAMES     CLERK  7698.0  1981-12-03 0:00   950     NaN      30
12   7902    FORD   ANALYST  7566.0  1981-12-03 0:00  3000     NaN      20
13   7934  MILLER     CLERK  7782.0  1982-01-23 0:00  1300     NaN      10

 -03 drop_duplicates

emp.drop_duplicates?

Signature:
emp.drop_duplicates(
    subset: 'Hashable | Sequence[Hashable] | None' = None,
    *,
    keep: 'DropKeep' = 'first',
    inplace: 'bool' = False,
    ignore_index: 'bool' = False,
) -> 'DataFrame | None'
Docstring:
Return DataFrame with duplicate rows removed.

 

emp.drop_duplicates(subset,                               # 중복체크 대상
                                   keep = 'first',                      # 중복제거시 남길 대상 선택
                                   ignore_index = False)       # 제거된 행이 발생하기 때문에 index 재배치 여부를 묻는다.

 

emp.iloc[0:1,:]  # slice를 사용하여 차원축소 방지
emp2 = pd.concat([emp, emp.iloc[0:1, :]], ignore_index =  True)

pd.concat([emp, emp.iloc[0:1, :]], ignore_index =  True)
Out[85]: 
    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
5    7698   BLAKE    MANAGER  7839.0  1981-05-01 0:00  2850     NaN      30
6    7782   CLARK    MANAGER  7839.0  1981-06-09 0:00  2450     NaN      10
7    7788   SCOTT    ANALYST  7566.0  1987-04-17 0:00  3000     NaN      20
8    7839    KING  PRESIDENT     NaN  1981-11-17 0:00  5000     NaN      10
9    7844  TURNER   SALESMAN  7698.0  1981-09-08 0:00  1500     0.0      30
10   7876   ADAMS      CLERK  7788.0  1987-05-23 0:00  1100     NaN      20
11   7900   JAMES      CLERK  7698.0  1981-12-03 0:00   950     NaN      30
12   7902    FORD    ANALYST  7566.0  1981-12-03 0:00  3000     NaN      20
13   7934  MILLER      CLERK  7782.0  1982-01-23 0:00  1300     NaN      10
14   7369   SMITH      CLERK  7902.0  1980-12-17 0:00   800     NaN      20

 ▲ 총 15행이 되었으며 마지막에 smith 행이 추가되었다.

 

emp2.drop_duplicates()

emp2.drop_duplicates()
Out[87]: 
    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
5    7698   BLAKE    MANAGER  7839.0  1981-05-01 0:00  2850     NaN      30
6    7782   CLARK    MANAGER  7839.0  1981-06-09 0:00  2450     NaN      10
7    7788   SCOTT    ANALYST  7566.0  1987-04-17 0:00  3000     NaN      20
8    7839    KING  PRESIDENT     NaN  1981-11-17 0:00  5000     NaN      10
9    7844  TURNER   SALESMAN  7698.0  1981-09-08 0:00  1500     0.0      30
10   7876   ADAMS      CLERK  7788.0  1987-05-23 0:00  1100     NaN      20
11   7900   JAMES      CLERK  7698.0  1981-12-03 0:00   950     NaN      30
12   7902    FORD    ANALYST  7566.0  1981-12-03 0:00  3000     NaN      20
13   7934  MILLER      CLERK  7782.0  1982-01-23 0:00  1300     NaN      10

 ▲ 중복되었던 smith 행이 제거되어 14개 행이 리턴되었다. 각 행의 모든 값이 같을 때 중복으로 인정하였다.

 

emp.drop_duplicates('JOB')

emp.drop_duplicates('JOB')
Out[88]: 
   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
3   7566  JONES    MANAGER  7839.0  1981-04-02 0:00  2975    NaN      20
7   7788  SCOTT    ANALYST  7566.0  1987-04-17 0:00  3000    NaN      20
8   7839   KING  PRESIDENT     NaN  1981-11-17 0:00  5000    NaN      10

 ▲ JOB 열 기준으로 중복되는 모든 행이 삭제되었으며, keep = 'first' (기본값) 옵션에 의해 유 니크한 값을 가진 행 중 가장 먼저 확인가능한 행만 남았다.

 

emp.drop_duplicates('JOB', keep = 'last')

emp.drop_duplicates('JOB', keep = 'last')
Out[89]: 
    EMPNO   ENAME        JOB     MGR         HIREDATE   SAL  COMM  DEPTNO
6    7782   CLARK    MANAGER  7839.0  1981-06-09 0:00  2450   NaN      10
8    7839    KING  PRESIDENT     NaN  1981-11-17 0:00  5000   NaN      10
9    7844  TURNER   SALESMAN  7698.0  1981-09-08 0:00  1500   0.0      30
12   7902    FORD    ANALYST  7566.0  1981-12-03 0:00  3000   NaN      20
13   7934  MILLER      CLERK  7782.0  1982-01-23 0:00  1300   NaN      10

 ▲ JOB 열 기준으로 중복되는 모든 행이 삭제되었으며, keep = 'last' 옵션에 의해 유니크한 값을 가진 행 중 가장 마지막 행만 남았다.


03 shift (데이터의 이동)

행 및 컬럼의 이동을 진행한다.

이전 / 이후 행과 비교, 연산, 출력 등을 위한 처리에 필요한 메서드이다.

pandas에서 제공하며 sql의 lag, lead와 같은 기능을 수행한다.

 

emp.shift(periods,      # 가져올 위치 (1이면 이전 값, 2이면 2개 이전 값)
                freq,           # 빈도
                axis = 0,     # 이동방향
                fill_value)   # 가져올 값이 없을 때의 대체값

 

 -01 이전값 가져오기
emp['SAL'].shift()

emp['SAL'].shift()
Out[91]: 
0        NaN
1      800.0
2     1600.0
3     1250.0
4     2975.0
5     1250.0
6     2850.0
7     2450.0
8     3000.0
9     5000.0
10    1500.0
11    1100.0
12     950.0
13    3000.0
Name: SAL, dtype: float64

 ▲ NA가 float type이므로 모두 float이 된다.

 

emp['SAL'].shift(2)

emp['SAL'].shift(2)
Out[92]: 
0        NaN
1        NaN
2      800.0
3     1600.0
4     1250.0
5     2975.0
6     1250.0
7     2850.0
8     2450.0
9     3000.0
10    5000.0
11    1500.0
12    1100.0
13     950.0
Name: SAL, dtype: float64

 

emp['SAL'].shift(2, fill_value=0)

emp['SAL'].shift(2, fill_value=0)
Out[93]: 
0        0
1        0
2      800
3     1600
4     1250
5     2975
6     1250
7     2850
8     2450
9     3000
10    5000
11    1500
12    1100
13     950
Name: SAL, dtype: int64

 ▲ NA가 0으로 치환되었다.


 -02 이후값 가져오기

emp['SAL'].shift(-1)

emp['SAL'].shift(-1)
Out[94]: 
0     1600.0
1     1250.0
2     2975.0
3     1250.0
4     2850.0
5     2450.0
6     3000.0
7     5000.0
8     1500.0
9     1100.0
10     950.0
11    3000.0
12    1300.0
13       NaN
Name: SAL, dtype: float64

 -03 컬럼의 이동

 

import numpy as np
from pandas import Series, DataFrame
df = DataFrame(np.arange(1,26).reshape(5,5), columns = list('ABCDE'))

DataFrame(np.arange(1,26).reshape(5,5), columns = list('ABCDE'))
Out[143]: 
    A   B   C   D   E
0   1   2   3   4   5
1   6   7   8   9  10
2  11  12  13  14  15
3  16  17  18  19  20
4  21  22  23  24  25

 

df.shift()

df.shift()
Out[145]: 
      A     B     C     D     E
0   NaN   NaN   NaN   NaN   NaN
1   1.0   2.0   3.0   4.0   5.0
2   6.0   7.0   8.0   9.0  10.0
3  11.0  12.0  13.0  14.0  15.0
4  16.0  17.0  18.0  19.0  20.0


df.shift(axis=1)

df.shift(axis=1)
Out[146]: 
    A   B   C   D   E
0 NaN   1   2   3   4
1 NaN   6   7   8   9
2 NaN  11  12  13  14
3 NaN  16  17  18  19
4 NaN  21  22  23  24

 -04 그룹별 이동

 

emp['SAL2'] = emp.groupby('DEPTNO')['SAL'].shift()
emp[['ENAME','DEPTNO','HIREDATE','SAL','SAL2']].sort_values('DEPTNO')

emp[['ENAME','DEPTNO','HIREDATE','SAL','SAL2']].sort_values('DEPTNO')
Out[150]: 
     ENAME  DEPTNO         HIREDATE   SAL    SAL2
4    CLARK      10  1981-06-09 0:00  2450     NaN
7     KING      10  1981-11-17 0:00  5000  2450.0
10  MILLER      10  1982-01-23 0:00  1300  5000.0
0    SMITH      20  1980-12-17 0:00   800     NaN
2    JONES      20  1981-04-02 0:00  2975   800.0
9     FORD      20  1981-12-03 0:00  3000  2975.0
12   SCOTT      20  1987-04-17 0:00  3000  3000.0
13   ADAMS      20  1987-05-23 0:00  1100  3000.0
1    ALLEN      30  1981-02-20 0:00  1600     NaN
3    BLAKE      30  1981-05-01 0:00  2850  1600.0
5   TURNER      30  1981-09-08 0:00  1500  2850.0
6   MARTIN      30  1981-09-28 0:00  1250  1500.0
8    JAMES      30  1981-12-03 0:00   950  1250.0
11    WARD      30  1982-02-22 0:00  1250   950.0

 

ex) 바로 직전에 입사한 상사보다 급여가 높은 직원의 이름, 부서번호, 급여 출력
emp = emp.sort_values('HIREDATE', ignore_index = True)
emp['MGR_SAL'] = emp['SAL'].shift(1)

emp.loc[emp['SAL'] > emp['MGR_SAL'], ['ENAME','DEPTNO','SAL']]

emp.loc[emp['SAL'] > emp['MGR_SAL'], ['ENAME','DEPTNO','SAL']]
Out[139]: 
    ENAME  DEPTNO   SAL
1   ALLEN      30  1600
2   JONES      20  2975
7    KING      10  5000
9    FORD      20  3000
12  SCOTT      20  3000

# [ 연습문제 - 빅분기 예상 문제 ] 
# sales4.csv 파일에는 날짜, 지점, 판매량 3개 컬럼이 있으며 날짜/지점 순으로 정렬되어 있다.
# 지점 코드는 B01~B10이고 날짜는 2025-04-01부터 한 달치 데이터다.
# 각 지점별로 전일 대비 판매량이 감소한 날짜 수를 집계하고,
# 감소 날짜가 가장 많은 지점을 출력한다.
# 감소폭은 (당일 판매량 - 전일 판매량) / 전일 판매량 * 100 으로 계산하며,
# 해당 지점의 가장 큰 감소율을 절댓값으로 소숫점 넷째 자리까지 반올림하여 출력한다.


df = pd.read_csv('sales4.csv')
df = df.sort_values(['지점','날짜'], ignore_index = True)

df.head()
Out[470]: 
           날짜   지점  판매량  전일판매량        증가율
0  2025-04-01  B01  170    NaN        NaN
1  2025-04-02  B01  156  170.0  -8.235294
2  2025-04-03  B01  136  156.0 -12.820513
3  2025-04-04  B01  136  136.0   0.000000
4  2025-04-05  B01  197  136.0  44.852941


df['전일판매량'] = df.groupby('지점')['판매량'].shift()
df['증가율'] = (df['판매량'] - df['전일판매량']) / df['전일판매량'] * 100

# 1) 감소 날짜가 가장 많은 지점
df2 = df.loc[df['증가율'] < 0 , : ]
result1 = df2['지점'].value_counts().idxmax()
print(result1)

# 2) 증가율
result2 = df2.loc[df2['지점'] == result1, '증가율'].abs().max()
print(round(result2, 4))


04 multi - level index

파이썬은 여러 depth (level)를 갖는 index를 구성할 수 있다.

level을 이용하여 level별 연산, 정렬, 그룹핑 등이 가능하다.

level 번호 가장 상위 레벨(바깥) 0, 1, 2, ...

 

 -01 생성

df.index = [a1,a2] # 2 level index
df.columns =  [a1,a2]

# 데이터 내의 특정 컬럼이 multi index가 되기를 원한다면 먼저 COL1, COL2 순서대로 정렬 후 인덱스 생성 권고!
df.set_index(['COL1','COL2'])

 

# ** INDEX 이름 부여
df.index.names = [상위레벨이름, 하위레벨이름]
df.columns.names = [상위레벨이름, 하위레벨이름]

df.index.names = [None, None]
df.columns.names = [None, None]


ex) index 직접 전달
df = DataFrame(np.arange(1,17).reshape(4,4))

df
Out[473]: 
    0   1   2   3
0   1   2   3   4
1   5   6   7   8
2   9  10  11  12
3  13  14  15  16


df.index = [['서울','서울','경기','경기'], ['C1','C2','C1','C2']]

df
Out[475]: 
         0   1   2   3
서울 C1  1   2   3   4
     C2  5   6   7   8
경기 C1  9  10  11  12
     C2  13  14  15  16

 

df.columns = [['2024','2024','2025','2025'], ['상반기','하반기','상반기','하반기']]

df
Out[479]: 
           2024          2025    
       상반기 하반기  상반기 하반기
서울 C1   1   2          3   4
     C2   5   6          7   8
경기 C1   9  10          11  12
     C2  13  14         15  16

 

df.index.names = ['지역','지점']
df.columns.names = ['연도','구분']

df
Out[482]: 
연도           2024     2025    
구분     상반기 하반기  상반기 하반기
지역 지점                  
서울  C1    1   2        3   4
      C2    5   6        7   8
경기  C1    9  10        11  12
      C2   13  14        15  16

ex)
df1 = pd.read_csv('병원현황.csv', encoding = 'cp949', skiprows=1)

df.head()
Out[500]: 
  시군구명칭     표시과목  2013. 4/4  ...  2009. 3/4  2009. 2/4  2009. 1/4
0   강남구        계       1343  ...     1135.0     1121.0     1097.0
1   강남구       내과         79  ...       71.0       72.0       71.0
2   강남구      신경과          6  ...        5.0        6.0        6.0
3   강남구  정신건강의학과         40  ...       42.0       42.0       40.0
4   강남구       외과         25  ...       19.0       18.0       18.0

df1 = df1.drop(columns = ['항목','단위'])

 

 

# 멀티 레벨 인덱스 설정

df1 = df1.set_index(['시군구명칭','표시과목'])

df.set_index(['시군구명칭','표시과목'])
Out[503]: 
                  2013. 4/4  2013. 3/4  ...  2009. 2/4  2009. 1/4
시군구명칭 표시과목                              ...                      
강남구   계                1343       1344  ...     1121.0     1097.0
      내과                 79         78  ...       72.0       71.0
      신경과                 6          6  ...        6.0        6.0
      정신건강의학과            40         41  ...       42.0       40.0
      외과                 25         25  ...       18.0       18.0
                    ...        ...  ...        ...        ...
금천구   핵의학과                0          0  ...        0.0        0.0
      가정의학과               0          0  ...        1.0        1.0
      예방의학과               0          0  ...        NaN        NaN
      전문과목미표시전문의         29         29  ...       25.0       25.0
      일반의                 9          9  ...       10.0       10.0

 

# 멀티 레벨 컬럼 설정
g1 = df1.columns.str[:4]
g2 = df1.columns.str[6]
df1.columns = [g1,g2]

df1
Out[541]: 
    시군구명        표시과목  2013              ...    2010    2009                        
     NaN         NaN     4     3     2  ...       1       4       3       2       1
0    강남구           계  1343  1344  1349  ...  1176.0  1155.0  1135.0  1121.0  1097.0
1    강남구          내과    79    78    78  ...    72.0    71.0    71.0    72.0    71.0
2    강남구         신경과     6     6     6  ...     5.0     5.0     5.0     6.0     6.0
3    강남구     정신건강의학과    40    41    41  ...    41.0    40.0    42.0    42.0    40.0
4    강남구          외과    25    25    24  ...    21.0    20.0    19.0    18.0    18.0
..   ...         ...   ...   ...   ...  ...     ...     ...     ...     ...     ...
645  금천구        핵의학과     0     0     0  ...     0.0     0.0     0.0     0.0     0.0
646  금천구       가정의학과     0     0     0  ...     1.0     1.0     1.0     1.0     1.0
647  금천구       예방의학과     0     0     0  ...     NaN     NaN     NaN     NaN     NaN
648  금천구  전문과목미표시전문의    29    29    29  ...    27.0    26.0    26.0    25.0    25.0
649  금천구         일반의     9     9    11  ...     8.0     9.0    10.0    10.0    10.0

 -02 색인

 1) 특정 컬럼 선택

df['2024']      # 상위레벨 컬럼 선택 (key indexing)

df['2024']      # 상위레벨 컬럼 선택 (key indexing)
Out[562]: 
구분     상반기  하반기
지역 지점          
서울 C1    1    2
   C2    5    6
경기 C1    9   10
   C2   13   14


df['상반기']     # 하위레벨 컬럼 선택 불가

df['상반기']     # 하위레벨 컬럼 선택 불가
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~\anaconda3\Lib\site-packages\pandas\core\indexes\base.py:3812, in Index.get_loc(self, key)
   3811 try:
-> 3812     return self._engine.get_loc(casted_key)
   3813 except KeyError as err:

File pandas/_libs/index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item()

File pandas/_libs/hashtable_class_helper.pxi:7096, in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: '상반기'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[563], line 1
----> 1 df['상반기']     # 하위레벨 컬럼 선택 불가

File ~\anaconda3\Lib\site-packages\pandas\core\frame.py:4112, in DataFrame.__getitem__(self, key)
   4110 if is_single_key:
   4111     if self.columns.nlevels > 1:
-> 4112         return self._getitem_multilevel(key)
   4113     indexer = self.columns.get_loc(key)
   4114     if is_integer(indexer):

File ~\anaconda3\Lib\site-packages\pandas\core\frame.py:4170, in DataFrame._getitem_multilevel(self, key)
   4168 def _getitem_multilevel(self, key):
   4169     # self.columns is a MultiIndex
-> 4170     loc = self.columns.get_loc(key)
   4171     if isinstance(loc, (slice, np.ndarray)):
   4172         new_columns = self.columns[loc]

File ~\anaconda3\Lib\site-packages\pandas\core\indexes\multi.py:3059, in MultiIndex.get_loc(self, key)
   3056     return mask
   3058 if not isinstance(key, tuple):
-> 3059     loc = self._get_level_indexer(key, level=0)
   3060     return _maybe_to_slice(loc)
   3062 keylen = len(key)

File ~\anaconda3\Lib\site-packages\pandas\core\indexes\multi.py:3410, in MultiIndex._get_level_indexer(self, key, level, indexer)
   3407         return slice(i, j, step)
   3409 else:
-> 3410     idx = self._get_loc_single_level_index(level_index, key)
   3412     if level > 0 or self._lexsort_depth == 0:
   3413         # Desired level is not sorted
   3414         if isinstance(idx, slice):
   3415             # test_get_loc_partial_timestamp_multiindex

File ~\anaconda3\Lib\site-packages\pandas\core\indexes\multi.py:2999, in MultiIndex._get_loc_single_level_index(self, level_index, key)
   2997     return -1
   2998 else:
-> 2999     return level_index.get_loc(key)

File ~\anaconda3\Lib\site-packages\pandas\core\indexes\base.py:3819, in Index.get_loc(self, key)
   3814     if isinstance(casted_key, slice) or (
   3815         isinstance(casted_key, abc.Iterable)
   3816         and any(isinstance(x, slice) for x in casted_key)
   3817     ):
   3818         raise InvalidIndexError(key)
-> 3819     raise KeyError(key) from err
   3820 except TypeError:
   3821     # If we have a listlike key, _check_indexing_error will raise
   3822     #  InvalidIndexError. Otherwise we fall through and re-raise
   3823     #  the TypeError.
   3824     self._check_indexing_error(key)

KeyError: '상반기'


df.loc['서울',:]           

df.loc['서울',:]           
Out[564]: 
연도 2024     2025    
구분  상반기 하반기  상반기 하반기
지점                  
C1    1   2    3   4
C2    5   6    7   8


df.loc['서울':'서울',:]    # error (level 유지 불가)

UnsortedIndexError: 'Key length (1) was greater than MultiIndex lexsort depth (0)'


차원을 유지하려는 행위가 슬라이스 인덱싱인데, 슬라이스 인덱싱으로 레벨을 유지하는 것은 불가능하다.
따라서 색인 메서드가 필요하다.


df.loc['C1',:]            # 하위레벨 인덱스 선택 불가

KeyError: 'C1'


df.iloc[[0,2],:]
df.iloc[:,[0,2]]

df.iloc[[0,2],:]    # 위치기반 선택 가능
Out[570]: 
연도    2024     2025    
구분     상반기 하반기  상반기 하반기
지역 지점                  
서울 C1    1   2    3   4
경기 C1    9  10   11  12

df.iloc[:,[0,2]]    # 위치기반 선택 가능
Out[571]: 
연도    2024 2025
구분     상반기  상반기
지역 지점          
서울 C1    1    3
   C2    5    7
경기 C1    9   11
   C2   13   15

 

# ** 멀티 레벨 인덱스 색인 메서드
df.xs(key,                           # 찾을 값
        axis = 0,                     # 방향 (0 : index, 1 : column)
        level = 0,                   # 레벨명 또는 레벨번호 전달
        drop_level = True)    # 레벨축소여부

 

df.xs('서울')

df.xs('서울')
Out[579]: 
연도 2024     2025    
구분  상반기 하반기  상반기 하반기
지점                  
C1    1   2    3   4
C2    5   6    7   8


df.xs('C1', level = 1)

df.xs('C1', level = 1)
Out[580]: 
연도 2024     2025    
구분  상반기 하반기  상반기 하반기
지역                  
서울    1   2    3   4
경기    9  10   11  12

 

df.xs('상반기', axis = 1, level = 1)

df.xs('상반기', axis = 1, level = '구분') 도 동일한 결과를 리턴한다.

df.xs('상반기', axis = 1, level = 1)
Out[581]: 
연도     2024  2025
지역 지점            
서울 C1     1     3
   C2     5     7
경기 C1     9    11
   C2    13    15

 

df.xs('서울', drop_level = False)

df.xs('서울', drop_level = False)
Out[583]: 
연도    2024     2025    
구분     상반기 하반기  상반기 하반기
지역 지점                  
서울 C1    1   2    3   4
   C2    5   6    7   8

 

# 단 하나의 .xs 메서드를 사용하여 index와 column 모두를 한 번에 선택할 수는 없다. (양방향 접근 불가능)

df.xs('C2', level = 1).xs('하반기', axis = 1, level = 1)

df.xs('C2', level = 1).xs('하반기', axis = 1, level = 1)
Out[584]: 
연도  2024  2025
지역            
서울     6     8
경기    14    16

 

# ** 멀티 레벨의 순차접근

df.index

멀티레벨의 경우 각 인덱스 값을 순서에 따라 튜플로 저장하고 있다.

df.index
Out[586]: 
MultiIndex([('서울', 'C1'),
            ('서울', 'C2'),
            ('경기', 'C1'),
            ('경기', 'C2')],
           names=['지역', '지점'])

 

df.columns
각 열의 경우도 마찬가지이다.

df.columns
Out[585]: 
MultiIndex([('2024', '상반기'),
            ('2024', '하반기'),
            ('2025', '상반기'),
            ('2025', '하반기')],
           names=['연도', '구분'])

 

df.loc[:, ('2024', '상반기')]

df.loc[:, ('2024', '상반기')]
Out[591]: 
지역  지점
서울  C1     1
    C2     5
경기  C1     9
    C2    13
Name: (2024, 상반기), dtype: int64


df.loc[[('서울', 'C1'),('경기', 'C2')], : ]

df.loc[[('서울', 'C1'),('경기', 'C2')], : ]
Out[592]: 
연도    2024     2025    
구분     상반기 하반기  상반기 하반기
지역 지점                  
서울 C1    1   2    3   4
경기 C2   13  14   15  16

[ 연습문제 ]
1) 강남구 병원 현황 선택
df1.xs('강남구')
df1.loc['강남구',:]

df1.loc['강남구',:]
Out[634]: 
            2013                    ...    2009                        
               4     3     2     1  ...       4       3       2       1
표시과목                                ...                                
계           1343  1344  1349  1322  ...  1155.0  1135.0  1121.0  1097.0
내과            79    78    78    78  ...    71.0    71.0    72.0    71.0
신경과            6     6     6     6  ...     5.0     5.0     6.0     6.0
정신건강의학과       40    41    41    40  ...    40.0    42.0    42.0    40.0
외과            25    25    24    26  ...    20.0    19.0    18.0    18.0
정형외과          28    28    28    28  ...    26.0    25.0    26.0    26.0
신경외과           3     3     3     3  ...     2.0     2.0     2.0     2.0
흉부외과           3     3     3     3  ...     3.0     3.0     3.0     3.0
성형외과         319   315   313   309  ...   260.0   251.0   241.0   238.0
마취통증의학과       11    12    12    13  ...    11.0    11.0    10.0    10.0
산부인과          46    48    48    49  ...    50.0    49.0    49.0    50.0
소아청소년과        28    29    28    27  ...    28.0    28.0    28.0    28.0
안과            68    67    67    66  ...    61.0    61.0    62.0    62.0
이비인후과         51    50    48    47  ...    45.0    45.0    43.0    42.0
피부과          119   120   123   122  ...   107.0   108.0   108.0   105.0
비뇨기과          35    37    38    38  ...    39.0    40.0    40.0    39.0
영상의학과         11    11    12    12  ...    11.0    12.0    12.0    12.0
병리과            3     4     4     4  ...     3.0     3.0     3.0     3.0
진단검사의학과        1     1     1     1  ...     1.0     1.0     1.0     1.0
결핵과            0     0     0     0  ...     0.0     0.0     0.0     0.0
재활의학과          8     8     8     8  ...    11.0    10.0    10.0     8.0
핵의학과           0     0     0     0  ...     0.0     0.0     0.0     0.0
가정의학과          7     7     7     8  ...     8.0     8.0     8.0     8.0
예방의학과          0     0     0     0  ...     NaN     NaN     NaN     NaN
전문과목미표시전문의   316   321   330   307  ...   243.0   244.0   239.0   230.0
일반의          136   130   127   127  ...   110.0    97.0    98.0    95.0


2) 2013년 병원 현황 선택
df1['2013']
df1.loc[:, '2013']
df1.xs('2013', axis = 1)

df1.xs('2013', axis = 1)
Out[635]: 
                     4     3     2     1
시군구명칭 표시과목                              
강남구   계           1343  1344  1349  1322
      내과            79    78    78    78
      신경과            6     6     6     6
      정신건강의학과       40    41    41    40
      외과            25    25    24    26
               ...   ...   ...   ...
금천구   핵의학과           0     0     0     0
      가정의학과          0     0     0     0
      예방의학과          0     0     0     0
      전문과목미표시전문의    29    29    29    30
      일반의            9     9    11    10


3) 1분기 병원 현황 선택
df1.xs('4', axis = 1, level = 1)

df1.xs('4', axis = 1, level = 1)
Out[636]: 
                  2013    2012    2011    2010    2009
시군구명칭 표시과목                                            
강남구   계           1343  1301.0  1271.0  1247.0  1155.0
      내과            79    77.0    73.0    74.0    71.0
      신경과            6     6.0     5.0     5.0     5.0
      정신건강의학과       40    39.0    37.0    38.0    40.0
      외과            25    26.0    25.0    24.0    20.0
               ...     ...     ...     ...     ...
금천구   핵의학과           0     0.0     0.0     0.0     0.0
      가정의학과          0     0.0     0.0     0.0     1.0
      예방의학과          0     NaN     NaN     NaN     NaN
      전문과목미표시전문의    29    30.0    30.0    28.0    26.0
      일반의            9    11.0     9.0    11.0     9.0


4) 강남구의 내과 현황을 출력
df1.xs('강남구').xs('내과')
df1.loc[[('강남구', '내과')],:]

df1.loc[[('강남구', '내과')],:]
Out[637]: 
           2013              2012        ...  2010        2009                  
              4   3   2   1     4     3  ...     2     1     4     3     2     1
시군구명칭 표시과목                               ...                                    
강남구   내과     79  78  78  78  77.0  74.0  ...  73.0  72.0  71.0  71.0  72.0  71.0

 -03 연산

df.sum()
df.sum(axis = 1)

df.sum()
Out[640]: 
연도    구분 
2024  상반기    28
      하반기    32
2025  상반기    36
      하반기    40
dtype: int64

df.sum(axis = 1)
Out[641]: 
지역  지점
서울  C1    10
    C2    26
경기  C1    42
    C2    58
dtype: int64


df.sum(level = 1)       # 수학통계메서드로는 level 연산 불가 -> groupby

TypeError: sum() got an unexpected keyword argument 'level'

 

df.groupby(axis=0, level=0).sum()   # 지역별 총합

df.groupby(axis=0, level=0).sum()
C:\Users\-\Temp\ipykernel_12988\2083092394.py:1: FutureWarning: The 'axis' keyword in DataFrame.groupby is deprecated and will be removed in a future version.
  df.groupby(axis=0, level=0).sum()
Out[645]: 
연도 2024     2025    
구분  상반기 하반기  상반기 하반기
지역                  
경기   22  24   26  28
서울    6   8   10  12


df.groupby(axis=1, level=0).sum()   # 연도별 총합

 => df.T.groupby(level=0).sum().T

 => 행렬전치를 사용한 코드 작성을 추천하고 있다.

df.groupby(axis=1, level=0).sum()
C:\Users\-\ipykernel_12988\997034833.py:1: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  df.groupby(axis=1, level=0).sum()
Out[646]: 
연도     2024  2025
지역 지점            
서울 C1     3     7
   C2    11    15
경기 C1    19    23
   C2    27    31

05 multi - level index의 처리

 

 -01 drop

df1.drop('계', axis=0, level=1)

df1.drop('계', axis=0, level=1)
Out[647]: 
                 2013              2012  ...  2010  2009                  
                    4   3   2   1     4  ...     1     4     3     2     1
시군구명칭 표시과목                               ...                              
강남구   내과           79  78  78  78  77.0  ...  72.0  71.0  71.0  72.0  71.0
      신경과           6   6   6   6   6.0  ...   5.0   5.0   5.0   6.0   6.0
      정신건강의학과      40  41  41  40  39.0  ...  41.0  40.0  42.0  42.0  40.0
      외과           25  25  24  26  26.0  ...  21.0  20.0  19.0  18.0  18.0
      정형외과         28  28  28  28  28.0  ...  25.0  26.0  25.0  26.0  26.0
              ...  ..  ..  ..   ...  ...   ...   ...   ...   ...   ...
금천구   핵의학과          0   0   0   0   0.0  ...   0.0   0.0   0.0   0.0   0.0
      가정의학과         0   0   0   0   0.0  ...   1.0   1.0   1.0   1.0   1.0
      예방의학과         0   0   0   0   NaN  ...   NaN   NaN   NaN   NaN   NaN
      전문과목미표시전문의   29  29  29  30  30.0  ...  27.0  26.0  26.0  25.0  25.0
      일반의           9   9  11  10  11.0  ...   8.0   9.0  10.0  10.0  10.0

 


 -02 sort_index

df1.sort_index(axis=1, level=1)   # 분기별로만 정렬되어서 출력 (1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4)
df1.sort_index(axis=1, level=0)   # 연도별, 분기별 정렬
df1.sort_index(axis=1, level=[0,1])  

df1.sort_index(axis=1, level=0)
Out[664]: 
                    2009                          ...  2013                  
                       1       2       3       4  ...     1     2     3     4
시군구명칭 표시과목                                        ...                        
강남구   계           1097.0  1121.0  1135.0  1155.0  ...  1322  1349  1344  1343
      내과            71.0    72.0    71.0    71.0  ...    78    78    78    79
      신경과            6.0     6.0     5.0     5.0  ...     6     6     6     6
      정신건강의학과       40.0    42.0    42.0    40.0  ...    40    41    41    40
      외과            18.0    18.0    19.0    20.0  ...    26    24    25    25
                 ...     ...     ...     ...  ...   ...   ...   ...   ...
금천구   핵의학과           0.0     0.0     0.0     0.0  ...     0     0     0     0
      가정의학과          1.0     1.0     1.0     1.0  ...     0     0     0     0
      예방의학과          NaN     NaN     NaN     NaN  ...     0     0     0     0
      전문과목미표시전문의    25.0    25.0    26.0    26.0  ...    30    29    29    29
      일반의           10.0    10.0    10.0     9.0  ...    10    11     9     9

 

df1.sort_index(axis=1, level=[0,1], ascending = [False, True])  # 연도별 내림차순, 분기별 오름차순 정렬

Out[665]: 
                  2013                    ...    2009                        
                     1     2     3     4  ...       1       2       3       4
시군구명칭 표시과목                                ...                                
강남구   계           1322  1349  1344  1343  ...  1097.0  1121.0  1135.0  1155.0
      내과            78    78    78    79  ...    71.0    72.0    71.0    71.0
      신경과            6     6     6     6  ...     6.0     6.0     5.0     5.0
      정신건강의학과       40    41    41    40  ...    40.0    42.0    42.0    40.0
      외과            26    24    25    25  ...    18.0    18.0    19.0    20.0
               ...   ...   ...   ...  ...     ...     ...     ...     ...
금천구   핵의학과           0     0     0     0  ...     0.0     0.0     0.0     0.0
      가정의학과          0     0     0     0  ...     1.0     1.0     1.0     1.0
      예방의학과          0     0     0     0  ...     NaN     NaN     NaN     NaN
      전문과목미표시전문의    30    29    29    29  ...    25.0    25.0    26.0    26.0
      일반의           10    11     9     9  ...    10.0    10.0    10.0     9.0

 -03 swaplevel
df1.T       # 행, 열 전치

 

Out[666]: 
시군구명칭      강남구                           ...  금천구                             
표시과목         계    내과  신경과 정신건강의학과    외과  ... 핵의학과 가정의학과 예방의학과 전문과목미표시전문의   일반의
2013 4  1343.0  79.0  6.0    40.0  25.0  ...  0.0   0.0   0.0       29.0   9.0
     3  1344.0  78.0  6.0    41.0  25.0  ...  0.0   0.0   0.0       29.0   9.0
     2  1349.0  78.0  6.0    41.0  24.0  ...  0.0   0.0   0.0       29.0  11.0
     1  1322.0  78.0  6.0    40.0  26.0  ...  0.0   0.0   0.0       30.0  10.0
2012 4  1301.0  77.0  6.0    39.0  26.0  ...  0.0   0.0   NaN       30.0  11.0
     3  1299.0  74.0  6.0    40.0  26.0  ...  0.0   0.0   NaN       30.0  11.0
     2  1295.0  74.0  7.0    39.0  24.0  ...  0.0   0.0   NaN       29.0  12.0
     1  1277.0  74.0  5.0    37.0  24.0  ...  0.0   0.0   NaN       29.0  11.0
2011 4  1271.0  73.0  5.0    37.0  25.0  ...  0.0   0.0   NaN       30.0   9.0
     3  1253.0  73.0  5.0    37.0  27.0  ...  0.0   0.0   NaN       31.0  11.0
     2  1303.0  76.0  5.0    39.0  25.0  ...  0.0   0.0   NaN       31.0  11.0
     1  1286.0  77.0  5.0    39.0  25.0  ...  0.0   0.0   NaN       31.0  11.0
2010 4  1247.0  74.0  5.0    38.0  24.0  ...  0.0   0.0   NaN       28.0  11.0
     3  1233.0  74.0  5.0    38.0  25.0  ...  0.0   0.0   NaN       28.0  10.0
     2  1214.0  73.0  5.0    39.0  22.0  ...  0.0   1.0   NaN       27.0   9.0
     1  1176.0  72.0  5.0    41.0  21.0  ...  0.0   1.0   NaN       27.0   8.0
2009 4  1155.0  71.0  5.0    40.0  20.0  ...  0.0   1.0   NaN       26.0   9.0
     3  1135.0  71.0  5.0    42.0  19.0  ...  0.0   1.0   NaN       26.0  10.0
     2  1121.0  72.0  6.0    42.0  18.0  ...  0.0   1.0   NaN       25.0  10.0
     1  1097.0  71.0  6.0    40.0  18.0  ...  0.0   1.0   NaN       25.0  10.0

 

df1.T       # 행, 열 전치
df1.swaplevel(i,             # 첫 번째에 위치할 레벨의 이름 또는 번호
                       j,             # 두 번째에 위치할 레벨의 이름 또는 번호
                       axis=0)   # 전치방향

 

df1.swaplevel(0,1)   # 전치의 의미가 없음 (상위레벨이 정렬되지 않았으므로)

df1.swaplevel(0,1)
Out[669]: 
                  2013                    ...    2009                        
                     4     3     2     1  ...       4       3       2       1
표시과목       시군구명칭                          ...                                
계          강남구    1343  1344  1349  1322  ...  1155.0  1135.0  1121.0  1097.0
내과         강남구      79    78    78    78  ...    71.0    71.0    72.0    71.0
신경과        강남구       6     6     6     6  ...     5.0     5.0     6.0     6.0
정신건강의학과    강남구      40    41    41    40  ...    40.0    42.0    42.0    40.0
외과         강남구      25    25    24    26  ...    20.0    19.0    18.0    18.0
               ...   ...   ...   ...  ...     ...     ...     ...     ...
핵의학과       금천구       0     0     0     0  ...     0.0     0.0     0.0     0.0
가정의학과      금천구       0     0     0     0  ...     1.0     1.0     1.0     1.0
예방의학과      금천구       0     0     0     0  ...     NaN     NaN     NaN     NaN
전문과목미표시전문의 금천구      29    29    29    30  ...    26.0    26.0    25.0    25.0
일반의        금천구       9     9    11    10  ...     9.0    10.0    10.0    10.0


df1.sort_index(level=[1,0]).swaplevel(0,1) # 정상적으로 리턴됨

df1.sort_index(level=[1,0])
Out[671]: 
            2013              2012        ...  2010        2009                  
               4   3   2   1     4     3  ...     2     1     4     3     2     1
시군구명칭 표시과목                                ...                                    
강남구   가정의학과    7   7   7   8   8.0   8.0  ...  10.0   9.0   8.0   8.0   8.0   8.0
강동구   가정의학과    5   5   5   5   5.0   5.0  ...   5.0   4.0   4.0   4.0   5.0   5.0
강북구   가정의학과    1   1   1   1   1.0   1.0  ...   4.0   4.0   4.0   4.0   4.0   4.0
강서구   가정의학과   19  19  18  17  17.0  17.0  ...  18.0  18.0  18.0  18.0  17.0  17.0
관악구   가정의학과    6   6   6   6   6.0   6.0  ...   6.0   7.0   7.0   7.0   7.0   7.0
         ...  ..  ..  ..   ...   ...  ...   ...   ...   ...   ...   ...   ...
용산구   흉부외과     0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
은평구   흉부외과     0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
종로구   흉부외과     0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
중구    흉부외과     0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
중랑구   흉부외과     0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0

[650 rows x 20 columns]

df1.sort_index(level=[1,0]).swaplevel(0,1)
Out[672]: 
            2013              2012        ...  2010        2009                  
               4   3   2   1     4     3  ...     2     1     4     3     2     1
표시과목  시군구명칭                               ...                                    
가정의학과 강남구      7   7   7   8   8.0   8.0  ...  10.0   9.0   8.0   8.0   8.0   8.0
      강동구      5   5   5   5   5.0   5.0  ...   5.0   4.0   4.0   4.0   5.0   5.0
      강북구      1   1   1   1   1.0   1.0  ...   4.0   4.0   4.0   4.0   4.0   4.0
      강서구     19  19  18  17  17.0  17.0  ...  18.0  18.0  18.0  18.0  17.0  17.0
      관악구      6   6   6   6   6.0   6.0  ...   6.0   7.0   7.0   7.0   7.0   7.0
         ...  ..  ..  ..   ...   ...  ...   ...   ...   ...   ...   ...   ...
흉부외과  용산구      0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
      은평구      0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
      종로구      0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
      중구       0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0
      중랑구      0   0   0   0   0.0   0.0  ...   0.0   0.0   0.0   0.0   0.0   0.0

[ 연습문제 ]
df1에서
df1.drop('계', axis=0, level=1)

1) 구별 병원개수 총합
df1.groupby(axis=0, level = 0).sum()

Out[659]: 
       2013                      2012  ...    2010    2009                        
          4     3     2     1       4  ...       1       4       3       2       1
시군구명칭                                  ...                                        
강남구    2686  2688  2698  2644  2602.0  ...  2352.0  2310.0  2270.0  2242.0  2194.0
강동구     674   666   662   650   650.0  ...   634.0   628.0   632.0   620.0   614.0
강북구     414   418   422   430   430.0  ...   442.0   436.0   428.0   428.0   424.0
강서구     626   626   634   624   624.0  ...   624.0   616.0   612.0   600.0   594.0
관악구     602   608   612   608   612.0  ...   624.0   610.0   610.0   604.0   608.0
광진구     502   498   496   490   488.0  ...   474.0   472.0   458.0   452.0   450.0
구로구     474   474   474   464   458.0  ...   464.0   464.0   460.0   456.0   450.0
금천구     264   262   264   266   270.0  ...   254.0   256.0   252.0   248.0   250.0
노원구     648   644   646   642   640.0  ...   660.0   652.0   646.0   638.0   624.0
도봉구     296   298   292   292   294.0  ...   310.0   306.0   306.0   310.0   310.0
동대문구    470   468   466   462   470.0  ...   484.0   488.0   484.0   474.0   480.0
동작구     476   480   478   478   478.0  ...   482.0   482.0   476.0   468.0   468.0
마포구     606   604   598   594   588.0  ...   562.0   564.0   562.0   556.0   550.0
서대문구    382   382   370   372   374.0  ...   382.0   382.0   396.0   392.0   400.0
서초구    1102  1088  1082  1074  1078.0  ...   954.0   938.0   922.0   916.0   900.0
성동구     338   342   346   344   338.0  ...   332.0   336.0   334.0   342.0   348.0
성북구     534   524   532   534   534.0  ...   522.0   524.0   530.0   532.0   528.0
송파구     880   872   878   880   866.0  ...   840.0   834.0   830.0   824.0   816.0
양천구     516   520   520   528   530.0  ...   514.0   514.0   512.0   506.0   510.0
영등포구    574   586   588   576   578.0  ...   578.0   564.0   554.0   552.0   548.0
용산구     222   224   228   228   234.0  ...   222.0   224.0   224.0   224.0   226.0
은평구     544   542   536   530   526.0  ...   510.0   502.0   504.0   508.0   504.0
종로구     330   338   348   346   346.0  ...   346.0   340.0   336.0   326.0   324.0
중구      422   420   424   428   420.0  ...   402.0   402.0   406.0   414.0   410.0
중랑구     428   422   420   422   426.0  ...   442.0   440.0   440.0   440.0   440.0


2) 연도별 병원개수 총합
df1.groupby(axis=1, level=0).sum()

Out[657]: 
                    2009    2010    2011    2012    2013
시군구명칭 표시과목                                              
강남구   계           4508.0  4870.0  5113.0  5172.0  5358.0
      내과           285.0   293.0   299.0   299.0   313.0
      신경과           22.0    20.0    20.0    24.0    24.0
      정신건강의학과      164.0   156.0   152.0   155.0   162.0
      외과            75.0    92.0   102.0   100.0   100.0
                 ...     ...     ...     ...     ...
금천구   핵의학과           0.0     0.0     0.0     0.0     0.0
      가정의학과          4.0     2.0     0.0     0.0     0.0
      예방의학과          0.0     0.0     0.0     0.0     0.0
      전문과목미표시전문의   102.0   110.0   123.0   118.0   117.0
      일반의           39.0    38.0    42.0    45.0    39.0


3) 표시과목별 연도별 총합
df1.groupby(axis=0, level=1).sum().groupby(axis=1, level=0).sum()

Out[656]: 
               2009     2010     2011     2012     2013
표시과목                                                   
가정의학과         752.0    758.0    725.0    704.0    731.0
결핵과             0.0      0.0      0.0      0.0      0.0
계           28255.0  29126.0  29560.0  29734.0  29962.0
내과           3543.0   3644.0   3697.0   3723.0   3788.0
마취통증의학과       505.0    547.0    598.0    646.0    695.0
병리과            20.0     26.0     25.0     30.0     28.0
비뇨기과          948.0    951.0    952.0    925.0    910.0
산부인과         1838.0   1796.0   1748.0   1683.0   1602.0
성형외과         1450.0   1550.0   1616.0   1631.0   1701.0
소아청소년과       2068.0   2099.0   2087.0   2067.0   2077.0
신경과           120.0    135.0    142.0    146.0    154.0
신경외과          198.0    207.0    212.0    204.0    197.0
안과           1562.0   1589.0   1604.0   1611.0   1616.0
영상의학과         220.0    212.0    196.0    189.0    182.0
예방의학과           0.0      0.0      0.0      0.0      2.0
외과            774.0    806.0    813.0    802.0    790.0
이비인후과        2095.0   2162.0   2239.0   2273.0   2290.0
일반의          2327.0   2390.0   2366.0   2430.0   2468.0
재활의학과         365.0    370.0    374.0    382.0    376.0
전문과목미표시전문의   5275.0   5609.0   5794.0   5856.0   5891.0
정신건강의학과       984.0    957.0    956.0    963.0    985.0
정형외과         1668.0   1692.0   1720.0   1710.0   1722.0
진단검사의학과        24.0     24.0     22.0     19.0     16.0
피부과          1493.0   1576.0   1644.0   1714.0   1713.0
핵의학과            0.0      0.0      0.0      0.0      0.0
흉부외과           26.0     26.0     30.0     26.0     28.0