상황: 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. 따라서 어떠한 언어가 컴파일 언어인지 인터프리터 언어인지를 따지는 것은 질문이 잘못 되었다. 이는 언어구현의 문제이기 때문이다.

 

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

소스코드를 바이트코드로 변환 후 이 바이트 코드를 해석기가 한번에 실행하는 방식이다.

물론 한줄한줄 해석기가 실행하는 방식도 가능하겠지만

(그리고 이렇게만 동작한다면 완전히 '인터프리터' 언어겠지만)

그렇게 하면 당연하게도 퍼포먼스에서 크게 손해를 보게 된다.

 

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

변수명을 짓는 건 창의적으로 했다간 고민도 많이 하거니와

다른 분들도 알아먹기 힘들기 때문에

점점 규칙을 가져가게 되는 것 같다.

 

그 중 날짜 변수에 대한 규칙을 갈무리 해본다.

보통 날짜 변수는 3가지 형태로 들어온다.

1. datetime 형태의 변수

 - datetime으로 들어오는 경우 포맷팅을 신경쓸 필요가 없다. 따라서 000_date로 변수명을 짓는다.

 - ex) start_date, checkin_date, end_date

2. timestamp 형태의 변수

 - 마찬가지로 포맷팅을 신경 안 쓴다.

 - ex) ts, start_ts, end_ts

3. string 형태의 변수

 - 포맷팅을 신경쓰게 된다. 어떻게 들어오는지 적어주는 게 좋다.

 - ex) target_dd, start_yyyymmdd, end_yymmdd

 

다른 규칙을 가져가시는 분은 댓글 부탁 드립니다. 구경 좀 하게요.

+ Recent posts