강의영상

- (1/4) 인사관리 예제 (1)

- (2/4) 인사관리 예제 (2)

- (3/4) 리스트의 상속

- (4/4) 사용자정의 자료형의 유용함

클래스공부 6단계

- 상속

인사관리 예제

- 아래와 같은 클래스를 만들자.

  • 이름, 직급, 연봉에 대한 정보가 있다.
  • 연봉을 올려주는 메소드가 존재함.
class Employee:
    def __init__(self,name,position=None,pay=0):
        self.name = name
        self.position = position
        self.pay = pay 
    def _repr_html_(self):
        html_str = """
        이름: {} <br/>
        직급: {} <br/>
        연봉: {} <br/>
        """.format(self.name,self.position,self.pay)
        return html_str
    def giveraise(self,pct): 
        self.pay = self.pay * (1+pct) 

- 확인

iu=Employee('iu',position='staff',pay=5000)
hynn=Employee('hynn',position='staff',pay=4000)
hd=Employee('hodong',position='mgr',pay=8000)
iu
이름: iu
직급: staff
연봉: 5000
iu.giveraise(0.1)
iu
이름: iu
직급: staff
연봉: 5500.0
hynn.giveraise(0.2)
hynn
이름: hynn
직급: staff
연봉: 4800.0

- 회사의 모든 직원의 연봉을 10%씩 올려보자.

iu=Employee('iu',position='staff',pay=5000)
hynn=Employee('hynn',position='staff',pay=4000)
hd=Employee('hodong',position='mgr',pay=8000)
for i in [iu, hynn, hd]:
    i.giveraise(0.1) 
iu
이름: iu
직급: staff
연봉: 5500.0
hynn
이름: hynn
직급: staff
연봉: 4400.0
hd
이름: hodong
직급: mgr
연봉: 8800.0

- 매니저직급은 일반직원들의 상승분에서 5%의 보너스가 추가되어 상승한다고 가정하고 모든 직원의 연봉을 10%씩 올리는 코드를 구현해보자.

(구현1)

iu=Employee('iu',position='staff',pay=5000)
hynn=Employee('hynn',position='staff',pay=4000)
hd=Employee('hodong',position='mgr',pay=8000)
for i in [iu,hynn,hd]: 
    if i.position == 'mgr':
        i.giveraise(0.1 + 0.05) 
    else: 
        i.giveraise(0.1) 
iu
이름: iu
직급: staff
연봉: 5500.0
hynn
이름: hynn
직급: staff
연봉: 4400.0
hd
이름: hodong
직급: mgr
연봉: 9200.0

(구현2) 새로운 클래스를 만들자

class Manager: 
    def __init__(self,name,position=None,pay=0):
        self.name = name
        self.position = position
        self.pay = pay 
    def _repr_html_(self):
        html_str = """
        이름: {} <br/>
        직급: {} <br/>
        연봉: {} <br/>
        """.format(self.name,self.position,self.pay)
        return html_str
    def giveraise(self,pct): 
        self.pay = self.pay * (1+pct+0.05)     
iu=Employee('iu',position='staff',pay=5000)
hynn=Employee('hynn',position='staff',pay=4000)
hd=Manager('hodong',position='mgr',pay=8000)
for i in [iu,hynn,hd]: 
    i.giveraise(0.1) 
iu
이름: iu
직급: staff
연봉: 5500.0
hynn
이름: hynn
직급: staff
연봉: 4400.0
hd
이름: hodong
직급: mgr
연봉: 9200.000000000002

(구현3) 상속이용!

class Manager(Employee): 
    def giveraise(self,pct): 
        self.pay = self.pay * (1+pct+0.05)     
iu=Employee('iu',position='staff',pay=5000)
hynn=Employee('hynn',position='staff',pay=4000)
hd=Manager('hodong',position='mgr',pay=8000)
for i in [iu,hynn,hd]:
    i.giveraise(0.1) 
iu
이름: iu
직급: staff
연봉: 5500.0
hynn
이름: hynn
직급: staff
연봉: 4400.0
hd
이름: hodong
직급: mgr
연봉: 9200.000000000002

- 요약: 이미 만들어진 클래스에서 대부분의 기능은 그대로 쓰지만 일부기능만 변경 혹은 추가하고 싶다면 클래스를 상속하면 된다!

리스트의 상속

- list와 비슷한데 멤버들의 빈도가 계산되는 메소드를 포함하는 새로운 나만의 list를 만들고 싶다.

lst = ['a','b','a','c','b','a','d']
lst
['a', 'b', 'a', 'c', 'b', 'a', 'd']

- 아래와 같은 딕셔너리를 만들고 싶다.

freq = {'a':3, 'b':2, 'c':1, 'd':1} 
freq
{'a': 3, 'b': 2, 'c': 1, 'd': 1}
  • lst.frequency()를 입력하면 위의 기능이 수행되도록 변형된 list를 쓰고 싶다.

- 구현

(시도1) 반쯤 성공?

lst
['a', 'b', 'a', 'c', 'b', 'a', 'd']
freq = {'a':0, 'b':0, 'c':0, 'd':0} 
freq
{'a': 0, 'b': 0, 'c': 0, 'd': 0}
for item in lst:
    freq[item] = freq[item] + 1 
freq
{'a': 3, 'b': 2, 'c': 1, 'd': 1}

(시도2) 실패

lst
['a', 'b', 'a', 'c', 'b', 'a', 'd']
freq = dict()
freq
{}
for item in lst:
    freq[item] = freq[item] + 1 
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_847290/251886214.py in <module>
      1 for item in lst:
----> 2     freq[item] = freq[item] + 1

KeyError: 'a'

에러이유? freq['a']를 호출할 수 없다 -> freq.get('a',0) 이용

freq['a']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_847290/3081565143.py in <module>
----> 1 freq['a']

KeyError: 'a'
freq.get?
Signature: freq.get(key, default=None, /)
Docstring: Return the value for key if key is in the dictionary, else default.
Type:      builtin_function_or_method
  • key에 대응하는 값이 있으면 그 값을 리턴하고 없으면 default를 리턴
freq.get('a') # freq['a']에 해당하는 자료가 없어도 에러가 나지 않음 
freq.get('a',0) # freq['a']에 해당하는 자료가 없어도 에러가 나지 않음 + freq['a']에 해당하는 자료가 없으면 0을 리턴
0

(시도3)

lst
['a', 'b', 'a', 'c', 'b', 'a', 'd']
freq = dict()
freq
{}
for item in lst:
    freq[item] = freq.get(item,0) + 1 
freq
{'a': 3, 'b': 2, 'c': 1, 'd': 1}

- 이것을 내가 정의하는 새로운 list의 메소드로 넣고 싶다.

class L(list): 
    def frequency(self):
        freq = dict()
        for item in self:
            freq[item] = freq.get(item,0) + 1 
        return freq 
lst = L([1,1,1,2,2,3])
lst # 원래 list에 있는 repr 기능을 상속받아서 이루어지는 결과
[1, 1, 1, 2, 2, 3]
lst?
Type:            L
String form:     [1, 1, 1, 2, 2, 3]
Length:          6
Docstring:       <no docstring>
Class docstring:
Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list.
The argument must be an iterable if specified.
_lst = L([4,5,6])
lst + _lst # L자료형끼리의 덧셈
[1, 1, 1, 2, 2, 3, 4, 5, 6]
lst + [4,5,6] # L자료형과 list자료형의 덧셈도 가능
[1, 1, 1, 2, 2, 3, 4, 5, 6]
  • L자료형의 덧셈은 list의 덧셈과 완전히 같음
lst.append(10) # append함수도 그대로 쓸 수 있음
lst
[1, 1, 1, 2, 2, 3, 10]

- 기존리스트에서 추가로 frequency() 메소드가 존재함.

lst.frequency()
{1: 3, 2: 2, 3: 1, 10: 1}

Appendix: 사용자정의 자료형의 유용함

- 사용자정의 자료형이 어떤 경우에는 유용할 수 있다.

import pandas as pd 
import numpy as np
import matplotlib.pyplot as plt

- 예제1

year = ['2016','2017','2017','2017',2017,2018,2018,2019,2019] 
value = np.random.randn(9)
df= pd.DataFrame({'year':year,'value':value})
df
year value
0 2016 -2.302688
1 2017 2.472312
2 2017 -0.026192
3 2017 -0.264954
4 2017 0.312092
5 2018 0.487921
6 2018 -0.372992
7 2019 -0.783505
8 2019 0.434551
plt.plot(df.year,df.value)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_847290/1597755330.py in <module>
----> 1 plt.plot(df.year,df.value)

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/pyplot.py in plot(scalex, scaley, data, *args, **kwargs)
   2755 @_copy_docstring_and_deprecators(Axes.plot)
   2756 def plot(*args, scalex=True, scaley=True, data=None, **kwargs):
-> 2757     return gca().plot(
   2758         *args, scalex=scalex, scaley=scaley,
   2759         **({"data": data} if data is not None else {}), **kwargs)

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/axes/_axes.py in plot(self, scalex, scaley, data, *args, **kwargs)
   1630         """
   1631         kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D)
-> 1632         lines = [*self._get_lines(*args, data=data, **kwargs)]
   1633         for line in lines:
   1634             self.add_line(line)

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/axes/_base.py in __call__(self, data, *args, **kwargs)
    310                 this += args[0],
    311                 args = args[1:]
--> 312             yield from self._plot_args(this, kwargs)
    313 
    314     def get_next_color(self):

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/axes/_base.py in _plot_args(self, tup, kwargs, return_kwargs)
    491 
    492         if self.axes.xaxis is not None:
--> 493             self.axes.xaxis.update_units(x)
    494         if self.axes.yaxis is not None:
    495             self.axes.yaxis.update_units(y)

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/axis.py in update_units(self, data)
   1447         neednew = self.converter != converter
   1448         self.converter = converter
-> 1449         default = self.converter.default_units(data, self)
   1450         if default is not None and self.units is None:
   1451             self.set_units(default)

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/category.py in default_units(data, axis)
    114         # the conversion call stack is default_units -> axis_info -> convert
    115         if axis.units is None:
--> 116             axis.set_units(UnitData(data))
    117         else:
    118             axis.units.update(data)

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/category.py in __init__(self, data)
    190         self._counter = itertools.count()
    191         if data is not None:
--> 192             self.update(data)
    193 
    194     @staticmethod

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/category.py in update(self, data)
    225         for val in OrderedDict.fromkeys(data):
    226             # OrderedDict just iterates over unique values in data.
--> 227             _api.check_isinstance((str, bytes), value=val)
    228             if convertible:
    229                 # this will only be called so long as convertible is True.

~/anaconda3/envs/csy/lib/python3.8/site-packages/matplotlib/_api/__init__.py in check_isinstance(_types, **kwargs)
     91                 names.remove("None")
     92                 names.append("None")
---> 93             raise TypeError(
     94                 "{!r} must be an instance of {}, not a {}".format(
     95                     k,

TypeError: 'value' must be an instance of str or bytes, not a int

에러의 이유: df.year에 str, int가 동시에 있음

np.array(df.year)
array(['2016', '2017', '2017', '2017', 2017, 2018, 2018, 2019, 2019],
      dtype=object)

자료형을 바꿔주면 해결할 수 있다.

np.array(df.year, dtype=np.float64)
#np.array(df.year).astype(np.float64)
#df.year.astype(np.float64)
array([2016., 2017., 2017., 2017., 2017., 2018., 2018., 2019., 2019.])
plt.plot(df.year.astype(np.float64),df.value,'.')
[<matplotlib.lines.Line2D at 0x7feaca4db760>]

- 예제2

year = ['2016','2017','2017','2017년','2017년',2018,2018,2019,2019] 
value = np.random.randn(9)
df= pd.DataFrame({'year':year,'value':value})
df
year value
0 2016 -0.678625
1 2017 -0.878156
2 2017 0.432160
3 2017년 0.519398
4 2017년 0.640570
5 2018 -0.904019
6 2018 1.148045
7 2019 -0.594485
8 2019 -1.015366
np.array(df.year,dtype=np.float64) # 타입을 일괄적으로 바꾸기 어렵다. 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_847290/3544192572.py in <module>
----> 1 np.array(df.year,dtype=np.float64) # 타입을 일괄적으로 바꾸기 어렵다.

~/anaconda3/envs/csy/lib/python3.8/site-packages/pandas/core/series.py in __array__(self, dtype)
    855               dtype='datetime64[ns]')
    856         """
--> 857         return np.asarray(self._values, dtype)
    858 
    859     # ----------------------------------------------------------------------

ValueError: could not convert string to float: '2017년'
L(df.year).frequency()
{'2016': 1, '2017': 2, '2017년': 2, 2018: 2, 2019: 2}
  • '2016'와 같은 형태, '2017년'와 같은 형태, 숫자형이 혼합 -> 맞춤형 변환이 필요함
'2017년'.replace("년","")
'2017'
L(df.year)
['2016', '2017', '2017', '2017년', '2017년', 2018, 2018, 2019, 2019]
def f(a): ## 사실 데이터의 구조를 모르면 이런 함수를 짤 수 없음 --> 자료의 구조를 확인해준다는 의미에서 freq가 있다면 편리하다. 
    if type(a) is str: 
        if "년" in a:
            return int(a.replace("년",""))
        else: 
            return int(a) 
    else: 
        return a 
[f(a) for a in df.year]
[2016, 2017, 2017, 2017, 2017, 2018, 2018, 2019, 2019]
df.year= [f(a) for a in df.year]
df
year value
0 2016 -0.678625
1 2017 -0.878156
2 2017 0.432160
3 2017 0.519398
4 2017 0.640570
5 2018 -0.904019
6 2018 1.148045
7 2019 -0.594485
8 2019 -1.015366
plt.plot(df.year, df.value, '.')
[<matplotlib.lines.Line2D at 0x7feaca3c73d0>]