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

#4 4일차_사용자 정의함수, groupby 연산, 조인

ecosso 2026. 5. 14. 16:13

01 사용자 정의함수

 -01 lambda

 -02 def

 -03 리스트 내포표현식 (List Comprehension)

 

02 groupby 

 -01 groupby 연산

 -02 외부 객체를 사용한 그룹핑

 

03 조인 (merge)


01 사용자 정의함수

 -01 lambda
 비교적 간단한 연산 결과 및 리턴값을 가지는 함수를 작성할 때 사용한다.
 변수선언, 자료구조 탐색 및 생성 등의 연산이 불가능하다.

 

* 사용법)
함수명 = lambda input : output
함수명 = lambda input : 참리턴 if 조건 else 거짓리턴

함수명 = lambda input : 참리턴1 if 조건1 else 참리턴2 if 조건2 else 거짓리턴 (권고 X)

 ▲ 지나치게 복잡해지며 가독성이 떨어지는 문제가 발생하기 시작한다.

 

* default  값 설정하기)
f_sum = lambda x, y=0 : x + y # default 값 설정을 통해 y를 생략할 수 있다.
f_sum(1,10)
f_sum(1)

 

※ default 값 설정 시 주의사항)

f_sum = lambda x=0, y : x + y 

f_sum = lambda x=0, y : x + y 
  Cell In[87], line 1
    f_sum = lambda x=0, y : x + y
                        ^
SyntaxError: parameter without a default follows parameter with a default

 ▲ 파이썬에서는 함수의 기본값 선언 시 앞의 인수가 기본값을 가지는 경우 반드시 그 뒤의 모 든 인수들이 기본값을 가져야 한다.

따라서 기본값 선언 시 뒤의 인수부터 순차적으로 지정하는 것이 좋다.


 -02 def

def 함수명(input1, input2, ...) :
      본문(변수선언, for문, if문, ...)
      return 대상

 

* defalut 값 설정하기)

def f_sub(x, y=0) :
    return x - y
f_sub(10,5)
f_sub(10)


ex) emp에서 10번 인사부, 그 외 총무부
import pandas as pd
emp = pd.read_csv('emp.csv')

# 방법1) lambda
emp['DEPTNO'][0]

# 조건을 통해 확인 후 출력하기 때문에 상수만이 사용 가능하다.
f1 = lambda x : '인사부' if x == 10 else '총무부'

# 상수의 경우 정상적으로 출력됨을 확인할 수 있다.
f1(10) # 정상
f1(30) # 정상

f1(10) # 정상
Out[41]: '인사부'

f1(30) # 정상
Out[42]: '총무부'


# 따라서 Series가 들어가면 오류가 발생한다.
f1(emp['DEPTNO']) # 불가

f1(emp['DEPTNO']) # 불가
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_23264\70618826.py in ?()
----> 1 f1(emp['DEPTNO']) # 불가

~\AppData\Local\Temp\ipykernel_23264\2778421134.py in ?(x)
----> 1 f1 = lambda x : '인사부' if x == 10 else '총무부'

~\anaconda3\Lib\site-packages\pandas\core\generic.py in ?(self)
   1578     @final
   1579     def __nonzero__(self) -> NoReturn:
-> 1580         raise ValueError(
   1581             f"The truth value of a {type(self).__name__} is ambiguous. "
   1582             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1583         )

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().


emp['DEPTNO'].map(f1) # 정상

emp['DEPTNO'].map(f1) # 정상
Out[44]: 
0     총무부
1     총무부
2     총무부
3     총무부
4     총무부
5     총무부
6     인사부
7     총무부
8     인사부
9     총무부
10    총무부
11    총무부
12    총무부
13    인사부
Name: DEPTNO, dtype: object

# 방법2) def
def f2(x) :
    if x == 10 :
        dname = '인사부'
    else :
        dname = '총무부'
    return dname

f2(10)

f2(10)
Out[46]: '인사부'


f2(emp['DEPTNO'])

f2(emp['DEPTNO'])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_23264\1340034630.py in ?()
----> 1 f2(emp['DEPTNO'])

~\AppData\Local\Temp\ipykernel_23264\653499240.py in ?(x)
      1 def f2(x) :
----> 2     if x == 10 :
      3         dname = '인사부'
      4     else :
      5         dname = '총무부'

~\anaconda3\Lib\site-packages\pandas\core\generic.py in ?(self)
   1578     @final
   1579     def __nonzero__(self) -> NoReturn:
-> 1580         raise ValueError(
   1581             f"The truth value of a {type(self).__name__} is ambiguous. "
   1582             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1583         )

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().


emp['DEPTNO'].map(f2)

마찬가지로 상수가 들어가야하는 상황이기에 map 매서드를 이용해야 정상적인 결과가 리턴됨을 확인할 수 있다.

emp['DEPTNO'].map(f2)
Out[48]: 
0     총무부
1     총무부
2     총무부
3     총무부
4     총무부
5     총무부
6     인사부
7     총무부
8     인사부
9     총무부
10    총무부
11    총무부
12    총무부
13    인사부
Name: DEPTNO, dtype: object

# 방법3) def + for
def f3(x):
    dname = []
    for i in x:
        if i == 10:
            dname.append('인사부')
        else:
            dname.append('총무부')
    return pd.Series(dname)

f3(emp['DEPTNO'])

f3(emp['DEPTNO'])
Out[70]: 
0     총무부
1     총무부
2     총무부
3     총무부
4     총무부
5     총무부
6     인사부
7     총무부
8     인사부
9     총무부
10    총무부
11    총무부
12    총무부
13    인사부
dtype: object

# 방법1) lambda
f1 = lambda x : 'A' if x >= 3000 else 'B'
emp['SAL'].map(f1)

emp['SAL'].map(f1)
Out[73]: 
0     B
1     B
2     B
3     B
4     B
5     B
6     B
7     A
8     A
9     B
10    B
11    B
12    A
13    B
Name: SAL, dtype: object


# 방법2) def
def f2(x) :
    if x >= 3000 :
        GRADE = 'A'
    else :
        GRADE = 'B'
    return GRADE

 

# 또는 x가 아니라 좀 더 직관적으로 sal 등으로 지정하는 것도 좋다.
def f_sal2(sal) :
    if sal >= 3000 :
        grade = 'A'
    else :
        grade = 'B'
    return grade
        
emp['SAL'].map(f2)

emp['SAL'].map(f2)
Out[80]: 
0     B
1     B
2     B
3     B
4     B
5     B
6     B
7     A
8     A
9     B
10    B
11    B
12    A
13    B
Name: SAL, dtype: object


# 방법3) def + for
def f3(x) :
    GRADE = []
    for i in x :
        if i >= 3000 :
            GRADE.append('A')
        else :
            GRADE.append('B')
    return pd.Series(GRADE)

f3(emp['SAL'])

f3(emp['SAL'])
Out[82]: 
0     B
1     B
2     B
3     B
4     B
5     B
6     B
7     A
8     A
9     B
10    B
11    B
12    A
13    B
dtype: object

ex) 두 객체의 동시 fetch map 적용
# 10번 부서원은 10% 증가, 그 외 부서는 20% 증가한 새로운 급여 출력하기

f_newsal = lambda sal, deptno : round(sal * 1.1) if deptno == 10 else round(sal * 1.2)
f_newsal(5000, 30) # 가능

f_newsal(5000, 30) # 가능
Out[92]: 6000


f_newsal(emp['SAL'], emp['DEPTNO']) # 불가

f_newsal(emp['SAL'], emp['DEPTNO']) # 불가
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_23264\1324258367.py in ?()
----> 1 f_newsal(emp['SAL'], emp['DEPTNO']) # 불가

~\AppData\Local\Temp\ipykernel_23264\3116933835.py in ?(sal, deptno)
----> 1 f_newsal = lambda sal, deptno : round(sal * 1.1) if deptno == 10 else round(sal * 1.2)

~\anaconda3\Lib\site-packages\pandas\core\generic.py in ?(self)
   1578     @final
   1579     def __nonzero__(self) -> NoReturn:
-> 1580         raise ValueError(
   1581             f"The truth value of a {type(self).__name__} is ambiguous. "
   1582             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1583         )

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

 

pd.Series(map(f_newsal, emp['SAL'], emp['DEPTNO']))

pd.Series(map(f_newsal, emp['SAL'], emp['DEPTNO']))
Out[95]: 
0      960
1     1920
2     1500
3     3570
4     1500
5     3420
6     2695
7     3600
8     5500
9     1800
10    1320
11    1140
12    3600
13    1430
dtype: int64

 

# ** 참고 : in R)
sapply(v1, f1)          # map 매서드
mapply(f1, v1, v2)   # map 함수


 -03 리스트 내포표현식 (List Comprehension)

리스트는 반복이 어려운 객체이다.
(담아서 표현하는 용도에 가까운 자료구조이므로 반복, 연산에 적합하지 않음)

 

그러나 리스트의 반복을 수행해야 하는 경우 리스트 내포표현식을 사용한다.

 

* 사용법)

[ for i in 대상 ]

result = []
for i in li :
    ...
    
li = [1,2,3,4,5]
li + 1  # 불가

li + 1 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[97], line 1
----> 1 li + 1 

TypeError: can only concatenate list (not "int") to list

 

# 방법1) for문
result = []
for i in li :
    result.append(i + 1)

result
Out[99]: [2, 3, 4, 5, 6]

 

# 방법2) 리스트 내포표현식
[i + 1 for i in li]

[i + 1 for i in li]
Out[102]: [2, 3, 4, 5, 6]

ex) 이메일 아이디 추출
email = ['abc@naver.com', 'a1004@gmail.com', 'p111@hanmail.net']

# 방법1) for문
result = []
for i in email :
    result.append(i.split('@')[0])

result
Out[106]: ['abc', 'a1004', 'p111']


# 방법2) 리스트 내포표현식
[i.split('@')[0] for i in email]

[i.split('@')[0] for i in email]
Out[107]: ['abc', 'a1004', 'p111']

02 groupby 

 -01 groupby 연산

pandas에서 제공하는 매서드로 dataframe에 적용한다.

 

emp.groupby(by,                           # 그룹핑 컬럼
                      axis,                        # 방향 (0 : 세로방향, 1 : 가로방향)
                      level,                       # 그룹핑할 레벨번호 또는 이름
                      as_index = True,     # 그룹핑컬럼  index 출력 여부 
                      sort = True,             # 정렬여부 
                      group_keys = True,  
                      dropna = True)       # NA 제외 여부


ex) DEPTNO 별 급여 총합
emp.groupby('DEPTNO')['SAL'].sum()

emp.groupby('DEPTNO')['SAL'].sum()
Out[114]: 
DEPTNO
10     8750
20    10875
30     9400
Name: SAL, dtype: int64


emp.groupby('DEPTNO', as_index = False)['SAL'].sum()

emp.groupby('DEPTNO', as_index = False)['SAL'].sum()
Out[115]: 
   DEPTNO    SAL
0      10   8750
1      20  10875
2      30   9400

ex) DEPTNO 별 SAL, COMM 총합
emp.groupby('DEPTNO')[['SAL', 'COMM']].sum()

 

emp.groupby('DEPTNO')[['SAL', 'COMM']].sum()
Out[119]: 
          SAL    COMM
DEPTNO               
10       8750     0.0
20      10875     0.0
30       9400  2200.0

ex) DEPTNO, JOB 별 SAL 평균
emp.groupby(['DEPTNO', 'JOB'])['SAL'].mean()

emp.groupby(['DEPTNO', 'JOB'])['SAL'].mean()
Out[118]: 
DEPTNO  JOB      
10      CLERK        1300.0
        MANAGER      2450.0
        PRESIDENT    5000.0
20      ANALYST      3000.0
        CLERK         950.0
        MANAGER      2975.0
30      CLERK         950.0
        MANAGER      2850.0
        SALESMAN     1400.0
Name: SAL, dtype: float64

 -02 외부 객체를 사용한 그룹핑
df = pd.DataFrame([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], columns = list('ABCD'))
groups = ['g1', 'g1', 'g2', 'g2']

df
Out[129]: 
    A   B   C   D
0   1   2   3   4
1   5   6   7   8
2   9  10  11  12
3  13  14  15  16


# groups는 객체이므로 ' '로 묶지 않는다.
df.groupby(groups).sum()

df.groupby(groups).sum()
Out[130]: 
     A   B   C   D
g1   6   8  10  12
g2  22  24  26  28

 

df.groupby(groups, axis = 1).sum()

df.groupby(groups, axis = 1).sum()
C:\Users\-\ipykernel_23264\3099643746.py:1: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  df.groupby(groups, axis = 1).sum()
Out[131]: 
   g1  g2
0   3   7
1  11  15
2  19  23
3  27  31

 

+) 행렬전치

df.T

df.T
Out[132]: 
   0  1   2   3
A  1  5   9  13
B  2  6  10  14
C  3  7  11  15
D  4  8  12  16

 

df.T.groupby(groups).sum()

df.T.groupby(groups).sum()
Out[133]: 
    0   1   2   3
g1  3  11  19  27
g2  7  15  23  31

 

예제) 아래 데이터에 대해 모든 연도의 상/하반기 실업율 평균 출력
df = pd.read_csv('2000-2013년_연령별실업율_40-49세.csv', encoding = 'cp949')

groups = ['상반기']*6 + ['하반기']*6
df.iloc[:,1:].groupby(groups).mean()

df.iloc[:,1:].groupby(groups).mean()
Out[158]: 
        2000년  2001년     2002년  ...     2011년     2012년     2013년
상반기  3.783333   3.55  2.216667  ...  2.316667  2.216667  2.133333
하반기  3.216667   2.45  1.783333  ...  1.966667  1.883333  1.816667

[2 rows x 14 columns]

 -03 기타 매서드

1) agg
여러 연산함수를 전달 시 사용한다.
emp.groupby('DEPTNO')['SAL'].agg(['max','min','sum','mean'])

emp.groupby('DEPTNO')['SAL'].agg(['max','min','sum','mean'])
Out[159]: 
         max   min    sum         mean
DEPTNO                                
10      5000  1300   8750  2916.666667
20      3000   800  10875  2175.000000
30      2850   950   9400  1566.666667


2) apply
각 그룹별 사용자 정의 함수의 적용을 위해 사용한다.
(그룹별 분리 - 함수적용 - 결합 결과 리턴)   
emp.head()      # 기본 : 위 5개 출력
emp.head(1)     # 개수 조절 가능

emp.head()      # 기본 : 위 5개 출력
Out[160]: 
   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

emp.head(1)     # 개수 조절 가능
Out[161]: 
   EMPNO  ENAME    JOB     MGR         HIREDATE  SAL  COMM  DEPTNO
0   7369  SMITH  CLERK  7902.0  1980-12-17 0:00  800   NaN      20

 

# 각 그룹별로 1행씩만 출력
emp.groupby('DEPTNO').apply(lambda x : x.head(1))

emp.groupby('DEPTNO').apply(lambda x : x.head(1))
C:\Users\-\2256954100.py:1: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  emp.groupby('DEPTNO').apply(lambda x : x.head(1))
Out[162]: 
          EMPNO  ENAME       JOB     MGR         HIREDATE   SAL   COMM  DEPTNO
DEPTNO                                                                        
10     6   7782  CLARK   MANAGER  7839.0  1981-06-09 0:00  2450    NaN      10
20     0   7369  SMITH     CLERK  7902.0  1980-12-17 0:00   800    NaN      20
30     1   7499  ALLEN  SALESMAN  7698.0  1981-02-20 0:00  1600  300.0      30

 

키가 DEPTNO임에도 불구하고 그룹키와 열이 동시에 출력되는 문제가 존재한다.

따라서 DEPTNO 컬럼이 index와 본문 모두에 출력되는 문제를 방지하고 싶다면 group_keys = False 옵션을 사용해야한다.

 +) 업데이트를 통해 해당 문제가 자동으로 해결될 예정이라는 메시지가 함께 출력됨을 확인할 수 있다.

emp.groupby('DEPTNO',group_keys = False).apply(lambda x : x.head(1))

emp.groupby('DEPTNO',group_keys = False).apply(lambda x : x.head(1))
C:\Users\-\4115926463.py:1: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  emp.groupby('DEPTNO',group_keys = False).apply(lambda x : x.head(1))
Out[163]: 
   EMPNO  ENAME       JOB     MGR         HIREDATE   SAL   COMM  DEPTNO
6   7782  CLARK   MANAGER  7839.0  1981-06-09 0:00  2450    NaN      10
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

 

include_groups = False를 사용하여 본문에서 DEPTNO 컬럼을 제외할 수도 있다.

emp.groupby('DEPTNO').apply(lambda x : x.head(1), include_groups=False)

emp.groupby('DEPTNO').apply(lambda x : x.head(1), include_groups=False)
Out[164]: 
          EMPNO  ENAME       JOB     MGR         HIREDATE   SAL   COMM
DEPTNO                                                                
10     6   7782  CLARK   MANAGER  7839.0  1981-06-09 0:00  2450    NaN
20     0   7369  SMITH     CLERK  7902.0  1980-12-17 0:00   800    NaN
30     1   7499  ALLEN  SALESMAN  7698.0  1981-02-20 0:00  1600  300.0

 

3) transform
원래 데이터의 크기를 유지하면서 그룹연산 결과를 가져다 주는 경우에 사용한다. (in R : transform)

ex) 부서별 최대급여
emp.groupby('DEPTNO')['SAL'].max()

emp.groupby('DEPTNO')['SAL'].max()
Out[193]: 
DEPTNO
10    5000
20    3000
30    2850
Name: SAL, dtype: int64


emp['MAX_SAL'] = emp.groupby('DEPTNO')['SAL'].transform('max')

# ** 최대 급여자 추출
emp.loc[emp['MAX_SAL'] == emp['SAL'], ['ENAME','SAL','DEPTNO']]

emp.loc[emp['MAX_SAL'] == emp['SAL'], ['ENAME','SAL','DEPTNO']]
Out[195]: 
    ENAME   SAL  DEPTNO
5   BLAKE  2850      30
7   SCOTT  3000      20
8    KING  5000      10
12   FORD  3000      20

ex) std에서 성별별로 몸무게가 가장 많이 나가는 학생의 이름, 성별(남자, 여자), 몸무게 출력
std = pd.read_csv('student.csv', encoding = 'cp949')

f1 = lambda x : '남자' if str(x)[6] == '1' else '여자'
std['성별'] = std['JUMIN'].map(f1)
std['MAX_WEIGHT'] = std.groupby('성별')['WEIGHT'].transform('max')
std.loc[std['MAX_WEIGHT'] == std['WEIGHT'], ['NAME', '성별',  'WEIGHT']]

std.loc[std['MAX_WEIGHT'] == std['WEIGHT'], ['NAME', '성별',  'WEIGHT']]
Out[245]: 
  NAME  성별  WEIGHT
3  김재수  남자      83
8  구유미  여자      58

03 조인 (merge)

pd.merge(left,                              # 첫번째 대상
                right,                            # 두번째 대상
                how,                            # 조인종류 (inner, left, right, outer, cross)
                on,                              # 조인키 (양쪽 조인키 이름이 같을 때)
                left_on,                       # 첫번째 대상의 조인키 (양쪽 조인키 이름이 다를 때)
                right_on,                     # 두번째 대상의 조인키 (양쪽 조인키 이름이 다를 때)
                left_index = False,     # index로 조인 시 사용
                right_index = False,   # index로 조인 시 사용
                sort = False,              # 정렬
                suffixes : ('_x','_y'))    # 구분자


ex) emp, dept를 조인하여 각 직원의 이름, 급여, 부서명 출력
emp = pd.read_csv('emp.csv')
dept = pd.read_csv('dept.csv')

pd.merge(emp, dept, on = 'DEPTNO')[['ENAME','SAL','DNAME']]

pd.merge(emp, dept, on = 'DEPTNO')[['ENAME','SAL','DNAME']]
Out[251]: 
     ENAME   SAL       DNAME
0    SMITH   800    RESEARCH
1    ALLEN  1600       SALES
2     WARD  1250       SALES
3    JONES  2975    RESEARCH
4   MARTIN  1250       SALES
5    BLAKE  2850       SALES
6    CLARK  2450  ACCOUNTING
7    SCOTT  3000    RESEARCH
8     KING  5000  ACCOUNTING
9   TURNER  1500       SALES
10   ADAMS  1100    RESEARCH
11   JAMES   950       SALES
12    FORD  3000    RESEARCH
13  MILLER  1300  ACCOUNTING

ex) std, exam을 조인하여 각 학생의 이름, 학년, 성적 출력
std = pd.read_csv('student.csv', encoding = 'cp949')
exam = pd.read_csv('exam_01.csv', encoding = 'cp949')

pd.merge(std, exam, on = 'STUDNO')[['NAME','GRADE','TOTAL']]

pd.merge(std, exam, on = 'STUDNO')[['NAME','GRADE','TOTAL']]
Out[256]: 
   NAME  GRADE  TOTAL
0   이진욱      4     84
1   서재수      4     78
2   이미경      4     83
3   김재수      4     62
4   박동호      4     88
5   김신영      3     92
6   신은경      3     87
7   오나라      3     81
8   구유미      3     79
9   임세현      3     95
10  일지매      2     89
11  김진욱      2     77
12  안광훈      2     86
13  김문호      2     82
14  노정호      2     87
15  이윤나      1     91
16  안은수      1     88
17  인영민      1     82
18  김주현      1     83
19   허우      1     97

ex) std, pro 조인하여 각 학생의 이름, 학년, 지도교수이름 출력
pro = pd.read_csv('professor.csv', encoding = 'cp949')
pd.merge(std, pro)     # NAME, PROFNO로 조인됨


pd.merge(std, pro, on = 'PROFNO') # inner join으로 인해 5개 행 생략 (지도교수가 없는 학생)

pd.merge(std, pro, on = 'PROFNO') # inner join으로 인해 5개 행 생략 (지도교수가 없는 학생)
Out[259]: 
    STUDNO NAME_x      ID_x  ...  DEPTNO                EMAIL                  HPAGE
0     9411    이진욱    75true  ...     101      captain@abc.net     http://www.abc.net
1     9412    서재수    pooh94  ...     102     lamb1@hamail.net                    NaN
2     9413    이미경  angel000  ...     103    naone10@empal.com                    NaN
3     9414    김재수  gunmandu  ...     201      chebin@daum.net                    NaN
4     9415    박동호   pincle1  ...     202  mypride@hanmail.net                    NaN
5     9511    김신영     bingo  ...     101       sweety@abc.net     http://www.abc.net
6     9512    신은경    jjang1  ...     102    number1@naver.com  http://num1.naver.com
7     9513    오나라     nara5  ...     202  mypride@hanmail.net                    NaN
8     9514    구유미    guyume  ...     301  silver-her@daum.net                    NaN
9     9515    임세현    shyun1  ...     201      chebin@daum.net                    NaN
10    9611    일지매  onejimae  ...     101       sweety@abc.net     http://www.abc.net
11    9612    김진욱  samjang7  ...     102     lamb1@hamail.net                    NaN
12    9613    안광훈   nonnon1  ...     201       gogogo@def.com                    NaN
13    9614    김문호     munho  ...     202  mypride@hanmail.net                    NaN
14    9615    노정호   star123  ...     301  silver-her@daum.net                    NaN

[15 rows x 21 columns]


pd.merge(std, pro, on = 'PROFNO', how = 'left') # left outer join으로 인해 생략된  5개 행 출력 (지도교수가 없는 학생)

pro = pd.read_csv('professor.csv', encoding = 'cp949')

pd.merge(std, pro, on = 'PROFNO', how = 'left')
Out[258]: 
    STUDNO NAME_x  ...                EMAIL                  HPAGE
0     9411    이진욱  ...      captain@abc.net     http://www.abc.net
1     9412    서재수  ...     lamb1@hamail.net                    NaN
2     9413    이미경  ...    naone10@empal.com                    NaN
3     9414    김재수  ...      chebin@daum.net                    NaN
4     9415    박동호  ...  mypride@hanmail.net                    NaN
5     9511    김신영  ...       sweety@abc.net     http://www.abc.net
6     9512    신은경  ...    number1@naver.com  http://num1.naver.com
7     9513    오나라  ...  mypride@hanmail.net                    NaN
8     9514    구유미  ...  silver-her@daum.net                    NaN
9     9515    임세현  ...      chebin@daum.net                    NaN
10    9611    일지매  ...       sweety@abc.net     http://www.abc.net
11    9612    김진욱  ...     lamb1@hamail.net                    NaN
12    9613    안광훈  ...       gogogo@def.com                    NaN
13    9614    김문호  ...  mypride@hanmail.net                    NaN
14    9615    노정호  ...  silver-her@daum.net                    NaN
15    9711    이윤나  ...                  NaN                    NaN
16    9712    안은수  ...                  NaN                    NaN
17    9713    인영민  ...                  NaN                    NaN
18    9714    김주현  ...                  NaN                    NaN
19    9715     허우  ...                  NaN                    NaN

[20 rows x 21 columns]

[ 연습 문제 ]
1. 부서별 최대 급여자의 이름, 부서번호, 급여를 출력 (groupby + merge)
1) 부서별 최대 급여 확인
emp_maxsal = emp.groupby('DEPTNO', as_index = False)['SAL'].max()
pd.merge(emp, emp_maxsal)

pd.merge(emp, emp_maxsal)
Out[265]: 
   EMPNO  ENAME        JOB     MGR         HIREDATE   SAL  COMM  DEPTNO
0   7698  BLAKE    MANAGER  7839.0  1981-05-01 0:00  2850   NaN      30
1   7788  SCOTT    ANALYST  7566.0  1987-04-17 0:00  3000   NaN      20
2   7839   KING  PRESIDENT     NaN  1981-11-17 0:00  5000   NaN      10
3   7902   FORD    ANALYST  7566.0  1981-12-03 0:00  3000   NaN      20


# in spl)
select
  from emp e, (select deptno, max(sal) as max_sal
                 from 
                 group by deptno) i
 where e.deptno = i.deptno
   and e.sal = i.max_sal;


2) std에서 학년별 성적이 가장 높은 학생의 이름, 학년, 성적 출력
df1 = pd.merge(std, exam, on = 'STUDNO')
bestscore = df1.groupby('GRADE', as_index = False)['TOTAL'].max()
pd.merge(df1, bestscore)[['NAME','GRADE','TOTAL']]


# 문제풀이
std2 = pd.merge(std, exam)[['NAME', 'GRADE', 'TOTAL']]
std_maxtotal = std2.groupby('GRADE', as_index = False)['TOTAL'].max()
pd.merge(std2, std_maxtotal)

pd.merge(df1, bestscore)[['NAME','GRADE','TOTAL']]
Out[327]: 
  NAME  GRADE  TOTAL
0  박동호      4     88
1  임세현      3     95
2  일지매      2     89
3   허우      1     97

3) std에서 학년별 평균 성적보다 성적이 낮은 학생의 이름, 학년 성적 출력 (non equi join)
std2['AVG_TOTAL'] = std2.groupby('GRADE', as_index = False)['TOTAL'].transform('mean')
std2.loc[std2['TOTAL'] < std2['AVG_TOTAL'], :]

std2.loc[std2['TOTAL'] < std2['AVG_TOTAL'], :]
Out[330]: 
   NAME  GRADE  TOTAL  AVG_TOTAL
1   서재수      4     78       79.0
3   김재수      4     62       79.0
7   오나라      3     81       86.8
8   구유미      3     79       86.8
11  김진욱      2     77       84.2
13  김문호      2     82       84.2
16  안은수      1     88       88.2
17  인영민      1     82       88.2
18  김주현      1     83       88.2

[ 연습 문제 - non equi join ]
# gogak, gift 사용해서 각 고객의 포인트 기준 받을 수 있는 상품의 이름을 하나씩 출력하기
gogak = pd.read_csv('gogak.csv', encoding = 'cp949')
gift = pd.read_csv('gift.csv', encoding = 'cp949')

gift.loc[(gogak['POINT'][0] > gift['G_START']) & (gogak['POINT'][0] < gift['G_END']), 'GNAME'].iloc[0]

f_gift = lambda x : gift.loc[(x > gift['G_START']) & (x < gift['G_END']), 'GNAME'].iloc[0]
gogak['POINT'].map(f_gift)

gogak['POINT'].map(f_gift)
Out[350]: 
0     양쪽문냉장고
1       참치세트
2     주방용품세트
3       참치세트
4       샴푸세트
5       샴푸세트
6     세차용품세트
7     주방용품세트
8     LCD모니터
9     세차용품세트
10      샴푸세트
11      참치세트
12    산악용자전거
13    세차용품세트
14    산악용자전거
15    LCD모니터
16       노트북
17       노트북
18     벽걸이TV
19     벽걸이TV
Name: POINT, dtype: object