SAS나 SQL에 익숙해져 있는 사람에게 가장 어려운 부분이 바로 pandas dataframe은 Series의 집합이므로 조건에 따라서 값을 변환하는 경우에는 정상적으로 작동하지 않는다. 아래 코드에서는 np.where() 가 SQL의 CASE WHEN TEN END 처럼 작동할 것이라고 생각했지만 그렇지 않다. 이렇게 row별로 컬럼의 값에 따라서 처리를 다르게 하기 위해서는 for row loop 문으로 처리해야 한다는 문제점이 있습니다. pandas dataframe 레벨에서 모든 문제를 해결하면 좋을텐데, 이게 그렇게 마음처럼 되지는 않으니까 루프문으로 데이터를 생성해야 하는 것입니다.
np.where(), np.select() 함수는 사용자 정의 함수를 이용해서 작성하는 것보다 훨씬 더 빠릅니다. 예전에는 df.assign() 함수를 이용해서 코드를 변환했는데 이것도 모든 것을 해결할 수 없으므로 이렇게 별도의 사용자 함수를 사용할 수 밖에 없습니다.
1. 필요한 라이브러리를 불러옵니다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
2. temp 데이터 프레임을 생성합니다. rgs_dt는 등록일인데, NULL 값일 때 '99999999'로 넣어준다.
temp=pd.DataFrame({'id': [1, 2, 3, 4, 5, 6],
'rgs_dt': ['20230101', '20230208', '20221208', '20221021', '20230331', '99999999']})
3. 기준월을 넣어 줍니다.
temp['bas_ym']='202303'
4. np.where()함수를 넣으면 정상 작동하지 않는다는 것을 알 수 있습니다. 에러 코드를 보면 pd.to_datetime()이 조건이 아니라 시리즈 전체에서 작동한다는 것을 알 수 있습니다.
temp['diff']=np.where(temp.rgs_dt=='99999999',
99999999,
pd.to_datetime(temp.bas_ym, format='%Y%m') - pd.to_datetime(temp.rgs_dt, format='%Y%m%d')
)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-4-d51adba5b206> in <cell line: 1>()
1 temp['diff']=np.where(temp.rgs_dt=='99999999',
2 99999999,
----> 3 pd.to_datetime(temp.bas_ym, format='%Y%m') - pd.to_datetime(temp.rgs_dt, format='%Y%m%d')
4 )
/usr/local/lib/python3.10/dist-packages/pandas/core/tools/datetimes.py in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
1066 result = arg.map(cache_array)
1067 else:
-> 1068 values = convert_listlike(arg._values, format)
1069 result = arg._constructor(values, index=arg.index, name=arg.name)
1070 elif isinstance(arg, (ABCDataFrame, abc.MutableMapping)):
/usr/local/lib/python3.10/dist-packages/pandas/core/tools/datetimes.py in _convert_listlike_datetimes(arg, format, name, tz, unit, errors, infer_datetime_format, dayfirst, yearfirst, exact)
428
429 if format is not None:
--> 430 res = _to_datetime_with_format(
431 arg, orig_arg, name, tz, format, exact, errors, infer_datetime_format
432 )
/usr/local/lib/python3.10/dist-packages/pandas/core/tools/datetimes.py in _to_datetime_with_format(arg, orig_arg, name, tz, fmt, exact, errors, infer_datetime_format)
536
537 # fallback
--> 538 res = _array_strptime_with_fallback(
539 arg, name, tz, fmt, exact, errors, infer_datetime_format
540 )
/usr/local/lib/python3.10/dist-packages/pandas/core/tools/datetimes.py in _array_strptime_with_fallback(arg, name, tz, fmt, exact, errors, infer_datetime_format)
471
472 try:
--> 473 result, timezones = array_strptime(arg, fmt, exact=exact, errors=errors)
474 except OutOfBoundsDatetime:
475 if errors == "raise":
/usr/local/lib/python3.10/dist-packages/pandas/_libs/tslibs/strptime.pyx in pandas._libs.tslibs.strptime.array_strptime()
ValueError: unconverted data remains: 99
5. 2가지 변수를 추가해 줍니다.
A. 날짜의 차이는 아래와 같이 간단하게 ().days()로 계산이 가능합니다. :) 네, 아주 쉽지요.
B. 월의 차이를 계산하려고, #으로 막아 놓은 코드는 정상 작동하지 않습니다. ().days() 와 같은 쉬운 함수가 존재하지 않습니다.
C. 그래서 어쩔 수 없이 .year(), .month()를 갖고 계산하는 방법 외에는 없습니다.
temp['diff']=[99999999 if temp.loc[j, 'rgs_dt'] == '99999999'
else (pd.to_datetime(temp.loc[j, 'bas_ym'], format='%Y%m') - pd.to_datetime(temp.loc[j, 'rgs_dt'], format='%Y%m%d')).days
for j in temp.index
]
#temp['mcn']=[99999999 if temp.loc[j, 'rgs_dt'] == '99999999'
# else (pd.to_datetime(temp.loc[j, 'bas_ym'], format='%Y%m').to_period('M')
# - pd.to_datetime(temp.loc[j, 'rgs_dt'], format='%Y%m%d').to_period('M'))
# for j in temp.index
# ]
temp['mcn']=[99999999 if temp.loc[j, 'rgs_dt'] == '99999999'
else (pd.to_datetime(temp.loc[j, 'bas_ym'], format='%Y%m').year - pd.to_datetime(temp.loc[j, 'rgs_dt'], format='%Y%m%d').year)*12
+ pd.to_datetime(temp.loc[j, 'bas_ym'], format='%Y%m').month - pd.to_datetime(temp.loc[j, 'rgs_dt'], format='%Y%m%d').month
for j in temp.index
]
temp
1 | 20230101 | 202303 | 59 | <2 * MonthEnds> | 2 |
2 | 20230208 | 202303 | 21 | <MonthEnd> | 1 |
3 | 20221208 | 202303 | 83 | <3 * MonthEnds> | 3 |
4 | 20221021 | 202303 | 131 | <5 * MonthEnds> | 5 |
5 | 20230331 | 202303 | -30 | <0 * MonthEnds> | 0 |
6 | 99999999 | 202303 | 99999999 | 99999999 | 99999999 |
temp['mcn'].info()
<class 'pandas.core.series.Series'>
RangeIndex: 6 entries, 0 to 5
Series name: mcn
Non-Null Count Dtype
-------------- -----
6 non-null object
dtypes: object(1)
memory usage: 176.0+ bytes
pd.to_datetime('19010101', format='%Y%m%d')
Timestamp('1901-01-01 00:00:00')
예전에도 이렇게 코드를 어떻게 해서든 가볍게 짜려고 했지만, 오늘 반나절을 보내고 나서 왜 문제였는지 겨우 알 수 있게 되었습니다. 몇 번이나 코드를 수정했지만 뭔가 개선되는 것은 그리 많지 않았습니다. 그래도 이렇게 파이썬하고도 친해질 수 있어서 다행입니다. 예전에는 SAS나 임팔라만 써서 불안한 점이 있었는데, 지금은 참 이것저것 하는 일도 많고 데이터도 많아서 좋습니다.
'Python, PySpark' 카테고리의 다른 글
pm4py (0) | 2023.10.31 |
---|---|
2개의 다른 Decision Tree 의 Feature Importance 수기로 계산하기 (0) | 2023.10.23 |
파이썬 한권으로 끝내기 - 234페이지 (credit_final.csv) (0) | 2023.05.28 |
rownumber() over () in python pandas (0) | 2023.05.01 |
cosine similarity 계산 (0) | 2023.04.12 |