-
우리의 A씨는 투잡을 하는 영업사원입니다.
-
A씨라는 가상의 인물 계좌를 어떻게 만들었는지 공개합니다.
-
numpy.random,datetime을pandas.DataFrame에 적용한 결과입니다. -
전체 코드는 여기에서 다운로드 가능합니다.
-
일단 데이터를 만든다는 목표로 만든, 거의 라이브코딩 수준 코드입니다.
-
코드가 거칠더라도 양해바랍니다.
1. 투잡하는 영업사원 A씨

-
A씨는 투잡하는 영업사원입니다.
- 다니는 회사와 계약한 컨텐츠 제작업체로부터 급여가 들어옵니다.
- 계약이 성사되면 성과급을 받고 가끔 컨텐츠 중에 2차 판매가 발생합니다.
-
충동구매와 구매취소를 자주 합니다.
- 그 바람에 통장엔 카드사 입금액이 많습니다.
- 입금은 되었지만 소득은 아닙니다.
-
어린 자녀가 있습니다.
- 친척들이 놀러오셔서 아이에게 용돈을 주고 가십니다.
- 아이가 많이 어립니다. A씨가 출근길에 입금하고, 인터넷뱅킹으로 아이 계좌로 이체합니다.
- 입금은 되었지만 A씨 돈이 아닙니다.
2. A씨의 2020년
- 코딩할 준비를 합니다.
1 | %matplotlib inline |
2.1. 다시 보는 2020년 달력
- 2020년 달력을 한번 그려봅니다.
- python에서 기본으로 제공하는 calendar 모듈을 사용합니다.
1 | import calendar |

- 윤년이었습니다. 2월이 29일까지 있네요.
- 2020년은 366일이고, 수요일에 시작해서 목요일에 끝났습니다.
2.2. 통장 만들기
-
이제부터 계좌를 만듭니다.
-
계좌에는 거래일자가
2020-01-01형식으로 적혀있습니다. -
datetime 모듈을 사용해서 쉽게 만들어 봅니다.
-
숫자만 넣으면 형식을 맞춰 날짜를 찍어주도록 합니다.
-
datetime()명령과datetime.strftime()명령을 사용합니다.
1 | from datetime import datetime |
* 실행 결과:
1
2
1. time_init = 2020-01-01 00:00:00
2. time_init(str) = 2020-01-01
- string으로 표현된 날짜도 datetime으로 바꿀 수 있습니다.
datetime.strptime()을 사용합니다.
1 | time_init_datetime = datetime.strptime(time_init_str, "%Y-%m-%d") |
* 실행 결과:
1
3. time_init(datetime) = 2020-01-01 00:00:00
- 요일을 확인합니다.
datetime.weekday()를 사용합니다.- 근무일 계산에 중요한 요소입니다.
1 | time_init_weekday = time_init.weekday() |
* 실행 결과: 0(월요일)~6(일요일)까지 숫자로 표현됩니다.
1
2
time_init_weekday = time_init.weekday()
4. time_init(weekday) = 2
- 통장을 만드는데 날짜를 일일이 찍어주기 싫습니다.
forloop을 돌릴 수도 있겠지만 월별 날짜가 달라서 귀찮은 일이 생깁니다.- 특정 일자부터 며칠째를
datetime.timedelta로 계산하면 훨씬 편해집니다.
1 | from datetime import timedelta |
* 실행 결과: 59일째는 2월 29일입니다.
1
5. time_59 = 2020-02-29
- 이제 통장을 만듭니다. 거래일자부터.
1 | dates = [] |

3. A씨의 근로소득
3.1. 회사
3.1.1. 기본급
-
매달 200만원. 내규(?)로 인해 금액이 매달 10% 내에서 달라집니다.
-
월급날은 매달 21일입니다. 월급날 index를 확인합니다.
-
pandas.DataFrame의 string을 자를 때는Series.str.split()을 사용합니다. -
이 중에서 맨 오른쪽 데이터를 쓸 때는 다시
.str[-1]을 붙입니다.
1 | y2020["입금액"] = np.nan |
* 실행 결과:
1
Int64Index([20, 51, 80, 111, 141, 172, 202, 233, 264, 294, 325, 355], dtype='int64')
- A씨 회사는 월급날이 주말이면 그 전 금요일에 월급을 줍니다.
- 월급날이 무슨 요일인지 확인합니다.
- 월급날을
pandas.Series.loc[]로 특정하고 요일을 찾는weekday()를 걸어줍니다. - 날짜를 datetime 형식으로 바꾸기 위해
strptime()을 사이에 끼워줍니다.
1 | salary_w = y2020["거래일자"].loc[idx_salary0].apply(lambda x: datetime.strptime(x, '%Y-%m-%d').weekday()) |
* 실행 결과: 주말이 세 번 껴있습니다 (3월, 6월, 11월)
1
salary_w.weekday = [1 4 5 1 3 6 1 4 0 2 5 0]
- 주말이 낀 월급날을 그 앞 금요일로 옮겨줍니다.
- 현재의 index에서
weekday()-4만큼 빼주면 됩니다. - 조건문을 걸어 데이터를 바꿔줄 때는
numpy.where()가 좋습니다.
1 | idx_salary = np.where(salary_w > 4, salary_w.index-(salary_w-4), salary_w.index) |
* 실행 결과: 3, 6, 11월의 월급날 index가 바뀌었습니다.
1
array([ 20, 51, 79, 111, 141, 170, 202, 233, 264, 294, 324, 355])
- 이제 기본급을 받아봅시다.
- 매달 200만원을 기준으로 10%까지 더 받을 수 있습니다.
numpy.random.uniform()으로 균등한 운명의 여신의 손에 기본급을 맡깁니다.- 기본급의 통장 내역은 “우리회사_월”로 찍힙니다.
1 | # 2백만원 |

- 월급 한번 받기 힘듭니다.
- 1월 월급은 200만원 하고도 4천원입니다.
- 우리회사_01이라고 적혀있네요.
3.1.2. 출장비
-
A씨는 바쁩니다. 1년에 출장을 50번 다닙니다.
-
1회 출장시 밥 사먹으라고 일비 3만원이 나옵니다.
-
1회 이동을 기준으로 교통비 3만원이 정액 지급됩니다.
-
예를 들어 하루에 3군데를 가면 일비 + 4회 이동 = 15만원이 나옵니다.
-
주말에는 출장을 가지 않습니다. 공휴일에는 갑니다.
-
중복을 허용한 랜덤 선택은
numpy.random.choice()를 사용합니다. -
1년 달력에서 주말과 주중을 분리합니다.
1 | # 날짜를 요일로 변환 |
* 실행 결과: 출장을 간 날입니다.
1
array([ 86, 232, 12, 30, 254, 310, 50, 145, 160, 222, 244, 349, 181, 79, 49, 99, 20, 79, 155, 338, 223, 225, 7, 9, 225, 226, 274, 103, 356, 226, 118, 205, 174, 155, 285, 83, 82, 159, 36, 44, 110, 286, 104, 323, 330, 15, 126, 119, 16, 260])
- 출장 일정, 일별 출장 횟수와 일별 출장비를 계산합니다.
1 | idx_biztrip, num_biztrip = np.unique(idx_biztrip0, return_counts=True) |
* 실행 결과:
1
2
3
출장일 = ['2020-01-08' '2020-01-10' '2020-01-13' '2020-01-16' '2020-01-17' '2020-01-21' '2020-01-31' '2020-02-06' '2020-02-14' '2020-02-19' '2020-02-20' '2020-03-20' '2020-03-23' '2020-03-24' '2020-03-27' '2020-04-09' '2020-04-13' '2020-04-14' '2020-04-20' '2020-04-28' '2020-04-29' '2020-05-06' '2020-05-25' '2020-06-04' '2020-06-08' '2020-06-09' '2020-06-23' '2020-06-30' '2020-07-24' '2020-08-10' '2020-08-11' '2020-08-13' '2020-08-14' '2020-08-20' '2020-09-01' '2020-09-11' '2020-09-17' '2020-10-01' '2020-10-12' '2020-10-13' '2020-11-06' '2020-11-19' '2020-11-26' '2020-12-04' '2020-12-15' '2020-12-22']
하루 출장횟수 = [1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1]
출장비 = ['90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '120000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '120000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '120000', '120000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000', '90000']
-
A씨의 몸과 마음은 힘들겠지만 출장비가 쏠쏠해 보입니다.
-
출장비를 아껴서 가족들과 치킨먹을 생각에 미소가 지어집니다. :)
-
월급날과 겹치는 날을 확인합니다.
-
교집합 계산은
set()이 편리합니다.
1 | print(set(idx_biztrip) & set(idx_salary)) |
* 실행 결과: 2일 겹칩니다.
1
{20, 79}
-
출장비를 통장에 꽂아줄 차례입니다. 문제는, 월급을 입력하듯 입력하면 안됩니다.
-
이미 통장에 기본급이 들어와 있기 때문에 그냥 입력하면 겹치는 날짜 월급이 지워집니다.
- 출장일에 이전 내역이 있는지 확인하고,
- 없으면 "입금액"과 "적요"란에 해당 내역을 입력.
- 이전 내역이 있으면 이전 내역을 list로 변환해 출장비를 원소로 붙여줍니다.
-
출장비 입금 내역은 “출장_출장횟수”입니다.
1 | # version 1 |
- 아까 구해둔 출장일과 출장비를 통장에 추가하고 결과를 확인합니다.
1 | y2020 = put_income(y2020, idx_biztrip, pay_biz, "출장") |

- 의도대로 잘 반영되었습니다.
- 월급날과 겹친 날은 list 형태로 묶여 들어갔고,
- 그렇지 않은 날은 int와 string 형태로 저장되었습니다.
3.1.3. 성과급
-
계약 한건당 성과급을 1천만원 받습니다.
-
A씨의 최고 기록은 한 해에 5건 계약입니다. 너무 힘들어서 이 이상은 안하기로 했습니다.
-
경험상 N번째 계약 확률이 $$0.7^N$$입니다. 여러 건을 성사시킬만큼 집중하려면 에너지가 그만큼 필요한데 총 량에 한계가 있기 때문입니다.
-
A씨가 계약 건수를 늘리기보다 투잡을 시작하게 된 계기이기도 합니다.
-
2020년에 몇 건을 했을까요. 확률로 알아봅시다.
-
사건의 확률을 조작할 때도
numpy.random.choice()가 유용합니다.
1 | num_success = 0 |
* 실행 결과: 올해는 성과가 좋습니다!
1
계약 성공 횟수 = 3
- 그럼 계약이 성사된 날은 언제일까요? 주중이겠죠?
1 | idx_success = np.random.choice(workday, count) |
* 실행 결과: 이렇답니다. 날짜가 중요합니까. 통장에 넣을 index가 중요하지.
1
array([ 30, 212, 275])
- 이제 통장에 건당 1천만원을 넣어줍니다.
- 기본급과 출장비는 항목 옆에 숫자가 붙어있었지만, 성과급은 그냥 "성과급"만 찍힙니다.
- 코드를 예외처리 해야 합니다.
1 | # version 2 |
- 성과급 입금일이 출장이나 월급날과 겹친 날이 있는지 확인합니다.
1 | print(set(idx_biztrip) & set(idx_success)) |
* 실행 결과: 출장날과는 1월 31일 하루 겹쳤고, 월급날과는 겹치지 않았습니다.
1
2
{30}
set()
- 겹친 날, 잘 들어가는지 확인합니다.
1 | y2020 = put_income(y2020, idx_success, ["10000000"]*len(idx_success), "성과급", number=False) |
* 실행 결과: 출장비에 데미지를 주지 않고 잘 들어갔습니다.
1
2
3
4
거래일자 2020-01-31
입금액 [90000, 10000000]
적요 [출장_007, 성과급]
Name: 30, dtype: object
3.1.4. 회사 입금액
- 회사로부터의 입금액들이 잘 들어가 있는지 한번 확인해봅니다.
- 데이터가 들어가 있지 않은 날은 빼고 한번 봅시다.
dropna()로 결측치가 있는 날들을 모두 제외하고.head(10)으로 처음 10줄만 봅니다.
1 | y2020.dropna().head(10) |

- 우리의 A씨, 연초부터 열일했습니다.
- 1월에만 출장을 7번 다니면서 성과급도 받았네요.
3.2. 투잡
3.2.1. 컨텐츠 제작
-
N잡러의 시대에 A씨도 예외가 아닙니다.
-
출장가는 날을 빼고 웬만하면 컨텐츠를 만들어 올리려고 합니다.
-
그러나 사람인지라 매일은 못합니다. 60% 정도 하는 것 같네요.
-
출장가지 않은 날을 헤아립니다. 주말 포함입니다.
1 | idx_twojob_max = np.array(list(set(y2020.index) - set(idx_biztrip))) |
* 실행 결과: 1년 중 대부분입니다.
1
320
- 60% “쯤” 투잡을 뜁니다.
- 표준편차가 한 달(30일)인 정규분포를 사용해 투잡한 날짜를 랜덤하게 뽑아봅니다.
numpy.random.normal()을 사용합니다.
1 | num_twojob = int(np.random.normal(loc=0.5*len(idx_twojob_max*0.6), scale=30, size=1)) |
* 실행 결과: 본업이 조금 힘들었나봅니다. <b>52.5%</b>밖에 안했네요.
1
168
- 168일이 일년 중 언제인지를
numpy.random.choice()를 사용해 추출합니다. - 같은 날이 두 번 뽑히면 안되니
replace=False로 놓습니다. - 그리고 그 날 통장엔 “OO컨텐츠” 명의로 10만원이 입금됩니다.
1 | # 투잡 뛴 날짜 index |

- 설날도 일했군요. 힘내라고 응원해줍니다.
3.2.2. 2차 창작물
-
컨텐츠가 간혹(확률=1%) 부가가치를 창출합니다.
-
컨텐츠가 인기를 얻으면 기고 요청이나 강연이 들어오기도 합니다.
-
입금일은 컨텐츠 업로드 후 1주일 뒤고, 금액은 백만원입니다.
-
행운의 컨텐츠를 꼽아봅니다.
-
이번에도
numpy.random.choice()로 확률을 조정합니다.
1 | # 2차 창작물 뽑기 |
* 실행 결과: 1%의 확률을 뚫고 <b>총 168건 중 3건</b>이나 뽑혔습니다!
1
[60 70 93]
- 통장에 입력해야 하는데, 이번엔 상황이 조금 다릅니다.
- 이제까지는 지정된 레이블에 일련번호가 붙거나 말거나였습니다.
- 이번엔 입금 건 별로 레이블이 바뀝니다.
str이 아니라array-like형식으로 들어올 때 하나씩 골라 넣는 코드가 필요합니다.
1 | # version 3 |
- 코드를 수정했습니다. 이제 통장에 꽂아봅니다.
1 | num_idx_2nd = len(idx_2nd) |
- 잘 입금됐는지 확인합니다.
- 선정일 index [60, 70, 93]에서 일주일 뒤인 [67, 77, 100]을 확인합니다.
1 | y2020.loc[[67, 77, 100] |

- A씨, 잘 나가네요. 방송도 출연하고 출판계약도 했습니다.
3.3. 기타 입금액
-
소득이 정확히 얼마인지 알기 어렵게 하는 노이즈입니다.
-
온갖 카드 환급액, 가까운 친척들로부터의 아이 용돈.
-
금액을 떠나서 레이블이 다채로운 것이 문제입니다. 마구마구 넣어봅니다.
-
신용카드 거래취소 등입니다.
-
5개의 신용카드, 체크카드, 동네 마트 이름을 넣어줍니다.
-
환급액은 3만5천원부터 6만 5천원까지로 랜덤하게 200건을 만듭니다.
1 | # 신용카드 환불: 7가지 |
- A씨 자녀에게 어른들이 주신 용돈입니다.
- 할아버지할머니를 비롯한 5 종류의 어른들이 만원, 3만원, 5만원을 랜덤하게 주십니다.
1 | # 자녀 용돈: 5가지 |
- 통장 입금 내역을 생성합니다.
1 | # 카드 환불액 |

4. 통장 정리
- 은행 홈페이지나 앱에서 거래내역을 확인하면 모든 날짜가 있지 않습니다.
- 거래 내역이 없는 날은 날짜조차 없습니다.
pandas.DataFrame.dropna()로 입금액이 없는 날짜를 지워주고 확인합니다.
1 | y2020 = y2020.dropna() |

- 같은 날짜에 여러 건이 있는 날이 많습니다.
- 실제 통장에서는 한 줄에 한 건만 나와있습니다. 이렇게 만듭니다.
pandas.DataFrame.explode()를 사용해서 list마다 다른 행으로 만들고pandas.concat()으로 결합합니다.
1 | y2020 = pd.concat([y2020[["거래일자", "입금액"]].explode("입금액"), y2020["적요"].explode()], axis=1).reset_index(drop=True) |

- 총 입금액은 얼마일까요?
1 | y2020["입금액"] = y2020["입금액"].astype(int) |
* 실행 결과: 8천 7백만원이 넘었습니다!
1
총 입금액 = 87,112,099
- 이 글을 쓰면서 코드를 여러 번 다시 실행했습니다.
- 수많은 랜덤 함수들로 이전 글과는 완전히 다른 결과가 나왔습니다.
- 이번 생의 소득은 얼마인지 봅시다. 시각화 코드는 이전 글에 있습니다.

5. 결론
- 랜덤 함수를 사용해서 한 사람의 투잡 소득 데이터를 만들어 보았습니다.
- 이 작업을 여러번 반복해서 수백 수천명의 A씨를 만들면 재밌는 일을 할 수 있겠다는 생각이 들었습니다.
- 주어진 환경에서 원잡에 집중할지 vs 투잡을 뛰는게 나을지 확률적으로 판단할 수 있겠더군요.
- 전 세계의 수많은 A씨를 응원합니다.