강의영상

- (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)
Input In [137], in <cell line: 1>()
      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)
Input In [138], in <cell line: 1>()
----> 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 = 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 -0.053328
1 2017 -1.453440
2 2017 0.951534
3 2017 -0.479833
4 2017 -0.891801
5 2018 -0.841571
6 2018 -0.945223
7 2019 0.990534
8 2019 0.433971
plt.plot(df.year,df.value)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [157], in <cell line: 1>()
----> 1 plt.plot(df.year,df.value)

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/matplotlib/pyplot.py:3019, in plot(scalex, scaley, data, *args, **kwargs)
   3017 @_copy_docstring_and_deprecators(Axes.plot)
   3018 def plot(*args, scalex=True, scaley=True, data=None, **kwargs):
-> 3019     return gca().plot(
   3020         *args, scalex=scalex, scaley=scaley,
   3021         **({"data": data} if data is not None else {}), **kwargs)

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/matplotlib/axes/_axes.py:1605, in Axes.plot(self, scalex, scaley, data, *args, **kwargs)
   1363 """
   1364 Plot y versus x as lines and/or markers.
   1365 
   (...)
   1602 (``'green'``) or hex strings (``'#008000'``).
   1603 """
   1604 kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D)
-> 1605 lines = [*self._get_lines(*args, data=data, **kwargs)]
   1606 for line in lines:
   1607     self.add_line(line)

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/matplotlib/axes/_base.py:315, in _process_plot_var_args.__call__(self, data, *args, **kwargs)
    313     this += args[0],
    314     args = args[1:]
--> 315 yield from self._plot_args(this, kwargs)

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/matplotlib/axes/_base.py:496, in _process_plot_var_args._plot_args(self, tup, kwargs, return_kwargs)
    493     x, y = index_of(xy[-1])
    495 if self.axes.xaxis is not None:
--> 496     self.axes.xaxis.update_units(x)
    497 if self.axes.yaxis is not None:
    498     self.axes.yaxis.update_units(y)

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

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/matplotlib/category.py:109, in StrCategoryConverter.default_units(data, axis)
    107 # the conversion call stack is default_units -> axis_info -> convert
    108 if axis.units is None:
--> 109     axis.set_units(UnitData(data))
    110 else:
    111     axis.units.update(data)

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/matplotlib/category.py:185, in UnitData.__init__(self, data)
    183 self._counter = itertools.count()
    184 if data is not None:
--> 185     self.update(data)

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/matplotlib/category.py:220, in UnitData.update(self, data)
    217 convertible = True
    218 for val in OrderedDict.fromkeys(data):
    219     # OrderedDict just iterates over unique values in data.
--> 220     _api.check_isinstance((str, bytes), value=val)
    221     if convertible:
    222         # this will only be called so long as convertible is True.
    223         convertible = self._str_is_convertible(val)

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/matplotlib/_api/__init__.py:92, in check_isinstance(_types, **kwargs)
     90     names.remove("None")
     91     names.append("None")
---> 92 raise TypeError(
     93     "{!r} must be an instance of {}, not a {}".format(
     94         k,
     95         ", ".join(names[:-1]) + " or " + names[-1]
     96         if len(names) > 1 else names[0],
     97         type_name(type(v))))

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 0x7f68c5381ee0>]

- 예제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.502215
1 2017 -0.646435
2 2017 -0.991310
3 2017년 -1.202340
4 2017년 0.325500
5 2018 0.539920
6 2018 -1.565858
7 2019 -0.286726
8 2019 -1.780535
np.array(df.year,dtype=np.float64) # 타입을 일괄적으로 바꾸기 어렵다. 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [176], in <cell line: 1>()
----> 1 np.array(df.year,dtype=np.float64)

File ~/anaconda3/envs/py39/lib/python3.9/site-packages/pandas/core/series.py:872, in Series.__array__(self, dtype)
    825 def __array__(self, dtype: npt.DTypeLike | None = None) -> np.ndarray:
    826     """
    827     Return the values as a NumPy array.
    828 
   (...)
    870           dtype='datetime64[ns]')
    871     """
--> 872     return np.asarray(self._values, dtype)

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.502215
1 2017 -0.646435
2 2017 -0.991310
3 2017 -1.202340
4 2017 0.325500
5 2018 0.539920
6 2018 -1.565858
7 2019 -0.286726
8 2019 -1.780535
plt.plot(df.year, df.value, '.')
[<matplotlib.lines.Line2D at 0x7f68c33c6550>]