날짜 레벨에서 정책의 유효 기간을 체크하고 알람하거나, 적용해야 하는 비지니스 요구사항이 꽤 많다.

하다 보면 한번씩 순수한 '날짜'를 제대로 편집하지 않아서 임계점에서 이슈가 생기기 마련이다.

일반적으로는 datetime으로 변환해서 대소 비교를 하는데, 비교점을 만들 때 시간을 replace 하는 것을 깜빡하면 불연속적인 정책에서 문제가 생긴다.

예시) 2월 3일 시작 ~ 3월 24일 끝인 정책이 있다고 할 때 3월 24일에 시간을 제거하지 않고 체크하면 이 정책은 3월 24일 기준 유효하지 않은 것으로 판정

 

따라서 datetime 객체를 비교할 때는 어느 레벨에서 비교하는지 생각하고 습관적으로 그 아래 세부 날짜 info를 모두 0으로 만들어주는 습관이 필요하다.

상황: Sentry로 부터 오는 error log의 Json 파싱을 하는 과정에서 이슈별 파싱되는 컬럼이 달라, get & transform하는 function에서 explode한 후, 컬럼들을 표준화할 필요가 생김

col_list = ['a', 'b', 'c']

df = df.explode('d')
df = pd.concat([df.drop(['d'], axis=1), df['d'].apply(pd.Series)], axis=1)
df = df.reindex(columns=col_list, fill_value=0)

이 상황에서 대부분은 for문을 써도 크게 퍼포먼스가 떨어질 것 같지는 않지만, index를 다시 정리하는 걸 이용해 이렇게 추가할 수 있다는 게 신기해서 남겨둔다.

주의 사항: fill_value를 None으로 설정하면 인식 못 하고 pd.NaN이 된다. None을 채워야 한다면 후속 fillna 처리가 필요하다.

 

일반적인 reindex 사용은 row index의 결측값에 대해 ffill(직전값)이나 bfill(직후값)을 채우는 방식으로 보인다.

import numpy as np
import pandas as pd

# None과의 비교
print(None == None)  # True
print(None == np.nan)  # False
print(None == pd.NaT)  # False

# NaN과의 비교
print(np.nan == np.nan)  # False
print(np.nan == None)  # False
print(np.nan == pd.NaT)  # False

# NaT와의 비교
print(pd.NaT == pd.NaT)  # False
print(pd.NaT == None)  # False
print(pd.NaT == np.nan)  # False

None의 경우에만 그나마 == 비교가 가능하다.

다른 경우는 그 자신을 == 처리해도 False가 나온다.

[문제는 이래도 에러가 안 난다.]

nan이나 NaT같은 값들은 특정 value를 지니는 게 아니라, 애초에 비교 자체를 상정하지 않는, 일종의 상태 label 같은 느낌이다.

꼭 np.isnan()이나 pd.isna() 같은 걸로 비교하자.

 

type이 제대로 안 잡혀서 이슈가 있다면

type을 다 지정한 dict를 만들어서 매핑하는 게 가장 확실한 방법이다.

 

다만 시리즈 개수가 엄청 많은데, 최대한 자동으로 잡고 싶다면

df.convert_dtypes()를 쓰면 된다.

특히 엑셀이나 구글 시트에서 읽어온 dict를 dataframe으로 바꾸고 하는 과정에서

무조건 object로 잡히고 이걸 다시 각각 변환해줘야 하는 귀찮음이 있는데

이런 경우 용이하게 자동으로 잡아줄 수 있다.

케이스: 구글시트에서 여러 config 시트를 가져와 for문을 돌리면서 컬럼별 데이터 타입 변환

최초 접근은 이랬다.

dfs = [df1, df2, df3, df4]

for df in dfs:
	for col in df.columns:
    	if '날짜' in col or '일자' in col:
        	df[col] = pd.to_datetime(df[col])
        elif '금액' in col or '수수료율' in col or '분담률' in col:
        	df[col] = df[col].astype(float)
        else:
        	df[col] = df[col].astype(str)

inplace가 안 되는 현상(함수에서 가져다 쓰려고 하면 기존 object 형태)

찾아보니 iteration이 진행되는 전제에 문제가 있었다.

iteration은 기본적으로 해당 iteration의 item에 대한 기존값의 변경을 전제하지 않는다.

그걸 전제하면 update나 delete 행위가 iteration을 하는 변수 자체에 가해지는 경우,

최초에 설정된 iterator가 변경되기 때문이다. [ex) 1~5까지 반복하는데 첫번째 loop에서 4,5를 지워버리는 식]

그렇다고 모든 update 행위가 안 되는 것은 아니어 보인다. 논리적으로 성립 가능한 update 행위도 있을 수는 있다.

다만 이런 가능성은 습관적으로 배제하는 것이 좋다.

dfs = [df1, df2, df3, df4]
sheets = {}

for idx, df in enumerate(dfs):
	for col in df.columns:
    	if '날짜' in col or '일자' in col:
        	con_df[col] = pd.to_datetime(df[col])
        elif '금액' in col or '수수료율' in col or '분담률' in col:
        	df[col] = df[col].astype(float)
        else:
        	df[col] = df[col].astype(str)
    sheets[idx] = df

iteration을 수행하면서 해당 변수를 업데이트/삭제를 하는 경우 새로운 container에 담는 습관을 들여야 겠다.

(특히 형변환)

https://soooprmx.com/%ed%8c%8c%ec%9d%b4%ec%8d%ac%ec%9d%80-%ec%9d%b8%ed%84%b0%ed%94%84%eb%a6%ac%ed%84%b0%ec%96%b8%ec%96%b4%ec%9e%85%eb%8b%88%ea%b9%8c/

 

파이썬은 인터프리터언어입니까? · Wireframe

최근에 많이 보게 되는 질문 중 하나가 ‘파이썬은 인터프리터 언어입니까? 컴파일언어입니까?’라는 것이다. 개인적으로 이 질문은 사람을 참 난감하게 하는데, 어떻게 답해야하나에 앞서 아

soooprmx.com

 

요약하자면,

1. 컴파일이란 광의적으로 특정 언어를 다른 언어로 번역하는 의미를 지닌다.

2. 인터프리터란 즉각적으로 한줄한줄을 실행시키는 실행 형태를 의미한다.

3. 따라서 어떠한 언어가 컴파일 언어인지 인터프리터 언어인지를 따지는 것은 질문이 잘못 되었다. 이는 언어구현의 문제이기 때문이다.

 

파이썬이 일반적으로 동작하는 방식은,

[컴파일] 소스코드를 바이트코드로 변환 후

[인터프리터] 이 바이트 코드를 해석기가 한줄한줄 실행한다.

 

애초에 서로 다른, 순차적인 step을 비교하기 때문에 헷갈린다 [컴파일 -> 실행(컴파일러, 인터프리터) / 그냥 실행(인터프리터)]

대부분 요즘 나온 managed 언어들은 굳이 보자면 다 하이브리드라 봐야 한다.

 

today = date.today()
date_list = []

for n in range(1, 3):
	days_to_monday = (7 * n - today.weekday())	
	days_to_saturday = (13 * n - today.weekday())
	date_list.append(today + timedelta(days=day_to_monday))
	date_list.append(today + timedelta(days=day_to_saturday))
date_list.sort()

간단히 설명하자면,

weekday는 0부터 6까지의 정수값을 가지는 요일값이다.

0은 월요일이기 때문에 그 주의 월요일에 해당하는 날은

기준시점에서 weekday를 빼면 나오게 된다.

이때 다음주 월요일을 알고싶다면 여기서 7일을 더해주면 된다.

따라서 특정 주차 후 월요일을 알고 싶다면 n*7 - weekday 만큼을 기준 날짜에 더해주면 된다.

 

 

작업을 하다보면 코드로 매핑해야 되는 케이스가 자주 생긴다.

이럴 때 np.where를 쓸 수도 있지만 케이스가 4~5개 생기는 경우 소스 가독성, 퍼포먼스가 떨어지게 된다.[각주:1]

또 apply를 써서 한 줄 한 줄 dict.get()으로 넣으면 가독성은 좋지만 퍼포먼스가 떨어진다.[각주:2]

이때 가독성도 좋고 퍼포먼스도 좋은 함수가 있었다.

series.map이다.

매핑된 dict를 작성하고 map하면 코드매핑이 된 시리즈를 생성할 수 있다.

test_dict = {1: 'a', 2: 'b', 3: 'c', 4: 'd'}
df['colB'] = df['colA'].map(test_dict).fillna('매핑 안 되는 것들 Default value')

 

정리하자면,

1. 단순 dict 조건으로 매핑이 필요하면 series.map을 쓰자

2. 각 row별 함수를 적용해야 하는 경우는 apply를 쓰자

  1. [np.where(df['colA'] == 1, 'a', np.where(df['colA'] == 2, 'b', np.where(df[colA] == 3, .......)))] 무슨 프랙탈 구조처럼 된다. [본문으로]
  2. df['colB'] = df['colA'].apply(lambda x: test_dict.get(x, '기본값')) apply가 자유도는 높지만 퍼포먼스가 그리 좋진 않다. 어쩔 수 없이 row별 함수처리를 해야하거나 퍼포먼스 차이가 크지 않을 때만 사용하자. [본문으로]

'파이썬' 카테고리의 다른 글

파이썬을 인터프리터 언어라고 하지 말자  (0) 2023.08.18
주차별 특정 요일 따오기  (0) 2023.05.02
[Python] 날짜 변수명 짓기  (0) 2022.10.19
[Python] 데이터 언패킹  (0) 2022.10.19
파이썬 프로젝트 진행  (0) 2022.09.06

+ Recent posts