데이터 사이언스 100번의 노크(구조화 데이터 처리편) – Python Part 4 (Q61 to Q80)의 해설입니다.
처음에
- 먼저 다음 셀을 실행합니다.
- 필요한 라이브러리를 가져오고 데이터베이스에서 데이터를 읽습니다(PostgreSQL).
- 사용할 것으로 예상되는 라이브러리는 다음 셀에서 가져옵니다.
- 사용하려는 다른 라이브러리가 있는 경우 install.packages()를 사용하여 적절하게 설치합니다.
- 이름, 주소 등은 더미 데이터이며 실제 데이터가 아닙니다.
import os
import pandas as pd
import numpy as np
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
import math
import psycopg2
from sqlalchemy import create_engine
from sklearn import preprocessing
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
from sklearn.model_selection import TimeSeriesSplit
from imblearn.under_sampling import RandomUnderSampler
if 'PG_PORT' in os.environ:
pgconfig = {
'host': 'db',
'port': os.environ['PG_PORT'],
'database': os.environ['PG_DATABASE'],
'user': os.environ['PG_USER'],
'password': os.environ['PG_PASSWORD'],
}
# pd.read_sql용 커넥터
conn = psycopg2.connect(**pgconfig)
df_customer = pd.read_sql(sql='select * from customer', con=conn)
df_category = pd.read_sql(sql='select * from category', con=conn)
df_product = pd.read_sql(sql='select * from product', con=conn)
df_receipt = pd.read_sql(sql='select * from receipt', con=conn)
df_store = pd.read_sql(sql='select * from store', con=conn)
df_geocode = pd.read_sql(sql='select * from geocode', con=conn)
else:
if not os.path.exists('../data/'):
!git clone https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess
os.chdir('100knocks-preprocess/docker/work/answer')
dtype = {
'customer_id': str,
'gender_cd': str,
'postal_cd': str,
'application_store_cd': str,
'status_cd': str,
'category_major_cd': str,
'category_medium_cd': str,
'category_small_cd': str,
'product_cd': str,
'store_cd': str,
'prefecture_cd': str,
'tel_no': str,
'postal_cd': str,
'street': str
}
df_customer = pd.read_csv("../data/customer.csv", dtype=dtype)
df_category = pd.read_csv("../data/category.csv", dtype=dtype)
df_product = pd.read_csv("../data/product.csv", dtype=dtype)
df_receipt = pd.read_csv("../data/receipt.csv", dtype=dtype)
df_store = pd.read_csv("../data/store.csv", dtype=dtype)
df_geocode = pd.read_csv("../data/geocode.csv", dtype=dtype)
연습문제
P-061: 영수증 내역 데이터(df_receipt)의 매출 금액(amount)을 고객 ID(customer_id)별로 합산하고, 매출 금액 합계를 상수 대수화(하단 10)하여 고객 ID, 매출 금액 합계와 함께 10건씩 표시한다. 단, 고객 ID가 “Z”로 시작하는 것은 비회원을 의미하므로 제외하여 계산한다.
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python'). \
groupby('customer_id'). \
agg({'amount':'sum'}).reset_index()
df_sales_amount['log_amount'] = np.log10(df_sales_amount['amount'] + 0.5)
df_sales_amount.head(10)
customer_id | amount | log_amount | |
---|---|---|---|
0 | CS001113000004 | 1298 | 3.113442 |
1 | CS001114000005 | 626 | 2.796921 |
2 | CS001115000010 | 3044 | 3.483516 |
3 | CS001205000004 | 1988 | 3.298526 |
4 | CS001205000006 | 3337 | 3.523421 |
5 | CS001211000025 | 456 | 2.659441 |
6 | CS001212000027 | 448 | 2.651762 |
7 | CS001212000031 | 296 | 2.472025 |
8 | CS001212000046 | 228 | 2.358886 |
9 | CS001212000070 | 456 | 2.659441 |
해설:
이 코드는 다음과 같은 동작을 수행합니다.
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")', engine='python')
df_receipt라는 DataFrame에서 customer_id 열이 "Z"로 시작하지 않는 행을 모두 선택한다. 완성된 DataFrame은 df_sales_amount로 저장된다.
df_sales_amount = df_sales_amount.groupby('customer_id').agg({'amount':'sum'}).reset_index()
df_sales_amount의 DataFrame을 customer_id로 그룹화하고, agg 메서드로 각 그룹의 금액 열의 합계를 계산합니다. 결과 DataFrame은 df_sales_amount로 저장됩니다.
df_sales_amount['log_amount'] = np.log10(df_sales_amount['amount'] + 0.5)
df_sales_amount DataFrame의 amount 열의 대수(하단 10)를 계산하는데, 0 또는 음수 값의 대수를 취하지 않기 위해 먼저 amount 값에 0.5를 더하고 있다. 결과 대수는 log_amount라는 새로운 컬럼에 저장됩니다.
df_sales_amount.head(10)
head 메서드를 사용하여 결과 df_sales_amount DataFrame의 처음 10줄을 표시합니다.
요약하면, 이 코드는 고객 ID가 "Z"로 시작하지 않는 DataFrame에서 행을 선택하고, 그 행을 고객 ID로 그룹화하고, 각 고객의 판매 금액의 합계를 계산하고, 판매 금액의 대수(10을 기준으로)를 취하여 결과 DataFrame의 처음 10개의 행을 표시합니다.
P-062: 영수증 내역 데이터(df_receipt)의 매출금액(amount)을 고객ID(customer_id)별로 합산하고, 매출금액 합계를 자연대수화(하단 e)하여 고객ID, 매출금액 합계와 함께 10건씩 표시한다. 단, 고객 ID가 “Z”로 시작하는 것은 비회원을 의미하므로 제외하여 계산한다.
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python'). \
groupby('customer_id'). \
agg({'amount':'sum'}).reset_index()
df_sales_amount['log_amount'] = np.log(df_sales_amount['amount'] + 0.5)
df_sales_amount.head(10)
customer_id | amount | log_amount | |
---|---|---|---|
0 | CS001113000004 | 1298 | 7.168965 |
1 | CS001114000005 | 626 | 6.440149 |
2 | CS001115000010 | 3044 | 8.021092 |
3 | CS001205000004 | 1988 | 7.595136 |
4 | CS001205000006 | 3337 | 8.112977 |
5 | CS001211000025 | 456 | 6.123589 |
6 | CS001212000027 | 448 | 6.105909 |
7 | CS001212000031 | 296 | 5.692047 |
8 | CS001212000046 | 228 | 5.431536 |
9 | CS001212000070 | 456 | 6.123589 |
해설:
이 코드는 df_receipt라는 pandas의 DataFrame에 대해 몇 가지 데이터 처리를 수행합니다.
먼저 customer_id 열이 "Z"로 시작하는 행을 필터링하고 있습니다. 이는 str.startswith() 메서드를 사용하여 조건부로 query() 메서드를 사용하고 있으며, engine='python' 인수는 문자열 조작에 기본 pandas 파서가 아닌 Python 파서를 사용하기 위해 제공되었습니다.
얻어진 DataFrame은 groupby() 메서드를 사용하여 customer_id 컬럼으로 그룹화된다.
agg() 메서드는 각 그룹의 금액 컬럼에 함수를 적용하는 데 사용된다. 이 경우 sum() 함수를 사용하여 각 고객의 매출 금액의 합계를 계산합니다.
그런 다음 reset_index()를 사용하여 결과 DataFrame을 재설정하고 새로운 인덱스를 갖도록 합니다.
마지막으로, amount 열의 자연대수에 0.5를 더한 값(0 또는 음수 값의 대수를 취하지 않기 위해)을 취하여 log_amount라는 새로운 열을 만듭니다.
결과 DataFrame의 처음 10개의 행은 head() 메서드를 사용하여 표시합니다.
P-063: 상품 데이터(df_product)의 단가(unit_price)와 원가(unit_cost)로부터 각 상품의 이익액을 산출하고, 그 결과를 10건 표시하시오.
df_tmp = df_product.copy()
df_tmp['unit_profit'] = df_tmp['unit_price'] - df_tmp['unit_cost']
df_tmp.head(10)
product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost | unit_profit | |
---|---|---|---|---|---|---|---|
0 | P040101001 | 04 | 0401 | 040101 | 198.0 | 149.0 | 49.0 |
1 | P040101002 | 04 | 0401 | 040101 | 218.0 | 164.0 | 54.0 |
2 | P040101003 | 04 | 0401 | 040101 | 230.0 | 173.0 | 57.0 |
3 | P040101004 | 04 | 0401 | 040101 | 248.0 | 186.0 | 62.0 |
4 | P040101005 | 04 | 0401 | 040101 | 268.0 | 201.0 | 67.0 |
5 | P040101006 | 04 | 0401 | 040101 | 298.0 | 224.0 | 74.0 |
6 | P040101007 | 04 | 0401 | 040101 | 338.0 | 254.0 | 84.0 |
7 | P040101008 | 04 | 0401 | 040101 | 420.0 | 315.0 | 105.0 |
8 | P040101009 | 04 | 0401 | 040101 | 498.0 | 374.0 | 124.0 |
9 | P040101010 | 04 | 0401 | 040101 | 580.0 | 435.0 | 145.0 |
해설:
이 코드는 다음과 같은 동작을 수행합니다.
df_tmp = df_product.copy()
df_product라는 DataFrame의 복사본을 만들어 df_tmp라는 새로운 변수에 대입하고 있습니다. 복사본은 원본 DataFrame을 변경하지 않도록 만들어졌습니다.
df_tmp['unit_profit'] = df_tmp['unit_price'] - df_tmp['unit_cost'].
이는 단가에서 단가를 빼서 각 상품의 단위당 이익을 계산하고, 그 결과를 unit_profit이라는 새로운 컬럼에 저장한다. 완성된 DataFrame은 df_tmp로 저장됩니다.
df_tmp.head(10)
head 메서드를 사용하여 df_tmp DataFrame의 처음 10개의 행을 표시하고 있습니다.
요약하면, 이 코드는 DataFrame의 복사본을 만들고, 단가에서 단가를 빼서 각 상품의 단위당 이익을 계산하고, 복사한 DataFrame에 단위당 이익이 있는 새로운 열을 추가하고, 결과 DataFrame의 처음 10줄을 표시하는 것입니다.
P-064: 상품 데이터(df_product)의 단가(unit_price)와 원가(unit_cost)에서 각 상품의 전체 평균 수익률을 계산하시오. 단, 단가와 원가에는 결손이 발생한다는 점에 유의하라.
df_tmp = df_product.copy()
df_tmp['unit_profit_rate'] = \
(df_tmp['unit_price'] - df_tmp['unit_cost']) / df_tmp['unit_price']
df_tmp['unit_profit_rate'].mean(skipna=True)
0.24911389885176904
해설:
이 코드는 다음과 같은 동작을 수행합니다.
df_tmp = df_product.copy()
df_product라는 DataFrame의 복사본을 만들어 df_tmp라는 새로운 변수에 대입하고 있습니다. 복사본은 원본 DataFrame을 변경하지 않도록 만들어졌습니다.
df_tmp['unit_profit_rate'] = (df_tmp['unit_price'] - df_tmp['unit_cost']) / df_tmp['unit_price'].
이익(단가에서 단가를 뺀 것)을 단가로 나누어 각 상품의 단위당 이익률을 계산하고, 그 결과를 unit_profit_rate라는 새로운 컬럼에 저장하고 있습니다. 완성된 DataFrame은 df_tmp로 저장됩니다.
df_tmp['unit_profit_rate'].mean(skipna=True)
df_tmp DataFrame의 unit_profit_rate 열의 평균값을 mean 방식으로 계산하고, 결손값(NaN)이 있으면 건너뛰고 결과를 표시한다.
요약하면, DataFrame의 복사본을 생성하고, 이익(단가에서 단가를 뺀 금액)을 단가로 나누어 각 상품의 단위당 이익률을 계산하고, 복사한 DataFrame에 단위당 이익률 열을 새로 추가하고, 이익률 열의 평균을 계산하고, 결과의 평균값을 표시하는 코드입니다.
P-065: 상품 데이터(df_product)의 각 상품에 대해 수익률이 30%가 되는 새로운 단가를 구하시오. 단, 1원 미만은 반올림한다. 그리고 10개의 결과를 표시하고, 수익률이 대략 30% 정도인 것을 확인하라. 단, 단가(unit_price)와 원가(unit_cost)에는 적자가 발생한다는 점에 유의하라.
df_tmp = df_product[['product_cd', 'unit_price', 'unit_cost']].copy()
df_tmp['new_price'] = np.floor(df_tmp['unit_cost'] / 0.7)
df_tmp['new_profit_rate'] = \
(df_tmp['new_price'] - df_tmp['unit_cost']) / df_tmp['new_price']
df_tmp.head(10)
product_cd | unit_price | unit_cost | new_price | new_profit_rate | |
---|---|---|---|---|---|
0 | P040101001 | 198.0 | 149.0 | 212.0 | 0.297170 |
1 | P040101002 | 218.0 | 164.0 | 234.0 | 0.299145 |
2 | P040101003 | 230.0 | 173.0 | 247.0 | 0.299595 |
3 | P040101004 | 248.0 | 186.0 | 265.0 | 0.298113 |
4 | P040101005 | 268.0 | 201.0 | 287.0 | 0.299652 |
5 | P040101006 | 298.0 | 224.0 | 320.0 | 0.300000 |
6 | P040101007 | 338.0 | 254.0 | 362.0 | 0.298343 |
7 | P040101008 | 420.0 | 315.0 | 450.0 | 0.300000 |
8 | P040101009 | 498.0 | 374.0 | 534.0 | 0.299625 |
9 | P040101010 | 580.0 | 435.0 | 621.0 | 0.299517 |
해설:
이 코드는 다음과 같은 동작을 수행합니다.
df_tmp = df_product[['product_cd', 'unit_price', 'unit_cost']].copy()
df_tmp라는 새로운 DataFrame을 생성하여 product_cd, unit_price, unit_cost라는 세 개의 열만 포함하도록 하고, 원래의 df_product DataFrame에서 이 열들을 복사한다.
df_tmp['new_price'] = np.floor(df_tmp['unit_cost'] / 0.7)
이는 단가를 0.7(마크업 계수 1.43)로 나누고 floor 메서드로 소수점 이하를 반올림하여 각 상품의 새로운 가격을 계산하고, 그 결과를 new_price라는 새로운 컬럼에 저장하고 있습니다. 결과 DataFrame은 df_tmp로 저장된다.
df_tmp['new_profit_rate'] = (df_tmp['new_price'] - df_tmp['unit_cost']) / df_tmp['new_price'].
이전 단계에서 계산한 새로운 가격을 사용하여 각 상품의 단위당 수익률을 계산하고, 그 결과를 new_profit_rate라는 새로운 컬럼에 저장합니다. 결과 DataFrame은 df_tmp로 저장됩니다.
df_tmp.head(10)
head 메서드를 사용하여 df_tmp DataFrame의 처음 10개의 행을 표시하고 있습니다.
요약하면, 이 코드는 세 개의 열을 가진 새로운 DataFrame을 만들고, 마크업 계수 1.43을 기반으로 각 상품의 새로운 가격을 계산하고, 새로운 가격을 사용하여 각 상품의 단위당 수익률을 계산하고, 복사한 DataFrame에 새로운 두 개의 열을 추가하고, 결과의 DataFrame의 처음 10개의 행을 표시합니다.
P-066: 상품 데이터(df_product)의 각 상품에 대해 수익률이 30%가 되는 새로운 단가를 구하시오. 이번에는 1원 미만은 반올림한다(반올림 또는 짝수로 반올림해도 무방하다). 그리고 10개의 결과를 표시하게 하고, 수익률이 대략 30% 정도인 것을 확인하라. 단, 단가(unit_price)와 원가(unit_cost)에는 결손이 발생한다는 점에 유의한다.
df_tmp = df_product[['product_cd', 'unit_price', 'unit_cost']].copy()
df_tmp['new_price'] = np.round(df_tmp['unit_cost'] / 0.7)
df_tmp['new_profit_rate'] = \
(df_tmp['new_price'] - df_tmp['unit_cost']) / df_tmp['new_price']
df_tmp.head(10)
product_cd | unit_price | unit_cost | new_price | new_profit_rate | |
---|---|---|---|---|---|
0 | P040101001 | 198.0 | 149.0 | 213.0 | 0.300469 |
1 | P040101002 | 218.0 | 164.0 | 234.0 | 0.299145 |
2 | P040101003 | 230.0 | 173.0 | 247.0 | 0.299595 |
3 | P040101004 | 248.0 | 186.0 | 266.0 | 0.300752 |
4 | P040101005 | 268.0 | 201.0 | 287.0 | 0.299652 |
5 | P040101006 | 298.0 | 224.0 | 320.0 | 0.300000 |
6 | P040101007 | 338.0 | 254.0 | 363.0 | 0.300275 |
7 | P040101008 | 420.0 | 315.0 | 450.0 | 0.300000 |
8 | P040101009 | 498.0 | 374.0 | 534.0 | 0.299625 |
9 | P040101010 | 580.0 | 435.0 | 621.0 | 0.299517 |
해설:
이 코드는 다음과 같은 동작을 수행합니다.
df_tmp = df_product[['product_cd', 'unit_price', 'unit_cost']].copy()
df_tmp라는 새로운 DataFrame을 생성하여 product_cd, unit_price, unit_cost 세 개의 열만 포함하도록 하고, 원래의 df_product DataFrame에서 이 열들을 복사한다.
df_tmp['new_price'] = np.round(df_tmp['unit_cost'] / 0.7)
이것은 단가를 0.7(마크업 계수 1.43)로 나누어 각 상품의 새로운 가격을 계산하고, round 메서드를 사용하여 가장 가까운 정수로 반올림하여 결과를 new_price라는 새로운 컬럼에 저장하고 있습니다. 결과 DataFrame은 df_tmp로 저장됩니다.
df_tmp['new_profit_rate'] = (df_tmp['new_price'] - df_tmp['unit_cost']) / df_tmp['new_price'].
이전 단계에서 계산한 새로운 가격을 사용하여 각 상품의 단위당 수익률을 계산하고, 그 결과를 new_profit_rate라는 새로운 컬럼에 저장합니다. 결과 DataFrame은 df_tmp로 저장됩니다.
df_tmp.head(10)
head 메서드를 사용하여 df_tmp DataFrame의 처음 10개의 행을 표시하고 있습니다.
요약하면, 이 코드는 세 개의 열을 가진 새로운 DataFrame을 생성하고, 마크업 계수 1.43과 가장 가까운 정수로 반올림하여 각 제품의 새로운 가격을 계산하고, 새로운 가격을 사용하여 각 제품의 단위당 수익률을 계산하고, 복사한 DataFrame에 새로운 두 개의 열을 추가하고, 결과 DataFrame의 처음 10개의 행을 표시하는 것입니다.
P-067: 상품 데이터(df_product)의 각 상품에 대해 수익률이 30%가 되는 새로운 단가를 구하시오. 이번에는 1원 미만은 반올림한다. 그리고 10개의 결과를 표시하고, 수익률이 대략 30% 정도인 것을 확인하라. 단, 단가(unit_price)와 원가(unit_cost)에는 적자가 발생하고 있다는 점에 유의하라.
df_tmp = df_product[['product_cd', 'unit_price', 'unit_cost']].copy()
df_tmp['new_price'] = np.ceil(df_tmp['unit_cost'] / 0.7)
df_tmp['new_profit_rate'] = \
(df_tmp['new_price'] - df_tmp['unit_cost']) / df_tmp['new_price']
df_tmp.head(10)
product_cd | unit_price | unit_cost | new_price | new_profit_rate | |
---|---|---|---|---|---|
0 | P040101001 | 198.0 | 149.0 | 213.0 | 0.300469 |
1 | P040101002 | 218.0 | 164.0 | 235.0 | 0.302128 |
2 | P040101003 | 230.0 | 173.0 | 248.0 | 0.302419 |
3 | P040101004 | 248.0 | 186.0 | 266.0 | 0.300752 |
4 | P040101005 | 268.0 | 201.0 | 288.0 | 0.302083 |
5 | P040101006 | 298.0 | 224.0 | 320.0 | 0.300000 |
6 | P040101007 | 338.0 | 254.0 | 363.0 | 0.300275 |
7 | P040101008 | 420.0 | 315.0 | 451.0 | 0.301552 |
8 | P040101009 | 498.0 | 374.0 | 535.0 | 0.300935 |
9 | P040101010 | 580.0 | 435.0 | 622.0 | 0.300643 |
해설:
이 코드는 df_product라는 pandas DataFrame 객체를 조작하고 있습니다.
아래 코드를 한 줄씩 분해해 보겠습니다.
df_tmp = df_product[['product_cd', 'unit_price', 'unit_cost']].copy(): 이 라인은 df_product의 하위 집합으로 "product_cd", "unit_price", "unit_cost"의 열만 를 포함하는 df_tmp라는 새로운 DataFrame 객체를 생성한다. .copy() 메서드는 원본 df_product를 참조하는 대신 DataFrame의 새로운 복사본을 생성하는 데 사용된다.
df_tmp['new_price'] = np.ceiling(df_tmp['unit_cost'] / 0.7): 이 행은 df_tmp에 'new_price'라는 새로운 열을 계산한다. 이는 'unit_cost' 열을 0.7로 나눈 후 numpy의 ceiling() 함수로 상한을 구한 결과입니다. 그러면 이윤율 30%를 기준으로 새로운 가격이 계산됩니다.
df_tmp['new_profit_rate'] = \ (df_tmp['new_price'] - df_tmp['unit_cost']) / df_tmp['new_price']: 이 행은 df_tmp에 'new_profit_rate'라는 새로운 열을 만들고 'new_price' 열에 'unit_cost'를 입력한다. new_price' 열에서 'unit_cost' 열을 빼고 'new_price' 열로 나눈 결과이다. 이를 통해 새로운 가격에 대한 수익률이 계산된다.
df_tmp.head(10): df_tmp DataFrame의 처음 10개의 행을 출력한다. 이 행은 이전 행의 계산이 제대로 이루어졌는지 확인하는 데 사용된다.
P-068: 상품 데이터(df_product)의 각 상품에 대해 소비세율 10%의 부가세 포함 금액을 구하고, 1원 미만의 단수는 절사하여 10개의 결과를 표시하시오. 단, 단가(unit_price)에는 결손이 발생한다는 점에 유의하라.
df_tmp = df_tmp = df_product[['product_cd', 'unit_price']].copy()
df_tmp['tax_price'] = np.floor(df_tmp['unit_price'] * 1.1)
df_tmp.head(10)
product_cd | unit_price | tax_price | |
---|---|---|---|
0 | P040101001 | 198.0 | 217.0 |
1 | P040101002 | 218.0 | 239.0 |
2 | P040101003 | 230.0 | 253.0 |
3 | P040101004 | 248.0 | 272.0 |
4 | P040101005 | 268.0 | 294.0 |
5 | P040101006 | 298.0 | 327.0 |
6 | P040101007 | 338.0 | 371.0 |
7 | P040101008 | 420.0 | 462.0 |
8 | P040101009 | 498.0 | 547.0 |
9 | P040101010 | 580.0 | 638.0 |
해설:
이 코드에서는 df_product라는 이름의 pandas DataFrame 객체도 조작하고 있습니다.
아래 코드를 한 줄씩 분해해 보겠습니다.
df_tmp = df_product[['product_cd', 'unit_price']].copy(): 이 줄은 df_product의 하위 집합으로 "product_cd"와 "unit_price" 컬럼만 포함하는 df_tmp라는 새로운 DataFrame 객체를 생성합니다. .copy() 메서드는 원래의 df_product를 참조하는 대신 DataFrame의 새로운 복사본을 생성하는 데 사용됩니다.
df_tmp['tax_price'] = np.floor(df_tmp['unit_price'] * 1.1): 이 행은 df_tmp에 'tax_price'라는 새로운 열을 계산한다. 이 열은 'unit_price' 열에 1.1을 곱하고 그 결과의 바닥을 numpy의 floor() 함수로 구한 결과입니다. 그러면 10%의 세금이 추가된 새로운 가격이 계산된다.
df_tmp.head(10): 이 행은 df_tmp의 DataFrame의 처음 10개의 행을 출력한다. 이전 행의 계산이 제대로 이루어졌는지 확인하는 데 사용합니다.
P-069: 영수증 내역 데이터(df_receipt)와 상품 데이터(df_product)를 결합하여 고객별 전체 상품 판매금액 합계와 카테고리 대분류 코드(category_major_cd)가 “07”(병조림 통조림)인 상품의 판매금액 합계를 계산한 후, 양자의 비율을 구하시오. 추출 대상은 카테고리 대분류 코드 “07”(병조림 통조림)의 판매 실적이 있는 고객으로 한정하고, 결과를 10건만 표시한다.
# 코드 예시 1
df_tmp_1 = df_receipt.groupby('customer_id').agg({'amount':'sum'}). \
reset_index().rename(columns={'amount':'sum_all'})
df_tmp_2 = pd.merge(df_receipt, df_product.query('category_major_cd == "07"'),
how='inner', on='product_cd').groupby('customer_id').\
agg({'amount':'sum'}).reset_index().\
rename(columns={'amount':'sum_07'})
df_tmp_3 = pd.merge(df_tmp_1, df_tmp_2, how='inner', on='customer_id')
df_tmp_3['sales_rate'] = df_tmp_3['sum_07'] / df_tmp_3['sum_all']
df_tmp_3.head(10)
customer_id | sum_all | sum_07 | sales_rate | |
---|---|---|---|---|
0 | CS001113000004 | 1298 | 1298 | 1.000000 |
1 | CS001114000005 | 626 | 486 | 0.776358 |
2 | CS001115000010 | 3044 | 2694 | 0.885020 |
3 | CS001205000004 | 1988 | 346 | 0.174044 |
4 | CS001205000006 | 3337 | 2004 | 0.600539 |
5 | CS001212000027 | 448 | 200 | 0.446429 |
6 | CS001212000031 | 296 | 296 | 1.000000 |
7 | CS001212000046 | 228 | 108 | 0.473684 |
8 | CS001212000070 | 456 | 308 | 0.675439 |
9 | CS001213000018 | 243 | 145 | 0.596708 |
해설:
이 코드에서는 pandas의 DataFrame 객체인 df_receipt와 df_product도 조작하고 있습니다.
아래 코드를 한 줄씩 분해해 보겠습니다.
df_tmp_1 = df_receipt.groupby('customer_id').agg({'amount':'sum'}).reset_index().rename(columns={'amount':'sum_all'}): 이 줄은 df_receipt DataFrame을 "customer_id"로 변경합니다. receipt DataFrame을 "customer_id"로 그룹화하여 그룹별로 "amount" 열의 합계를 계산하고, 그 결과 DataFrame의 인덱스를 재설정하고 있습니다. 이후 .rename() 메서드를 사용하여 결과 DataFrame의 이름을 "sum_all"로 변경합니다.
df_tmp_2 = pd.merge(df_receipt, df_product.query('category_major_cd == "07"'), how='inner', on='product_cd').groupby('customer_id').agg ({'amount': sum'}).reset_index().rename(columns={'amount': 'sum_07' }): 이 라인은 먼저 df_product를 필터링하여 category_major_cd 열이 "07"과 같은 행만 포함하도록 하도록 합니다. 그런 다음 이 필터링된 DataFrame을 내부 바인딩을 사용하여 "product_cd" 열의 df_receipt와 결합한다. 그런 다음 결과 DataFrame을 'customer_id'로 그룹화하여 그룹별로 'amount' 열의 합계를 계산하고 인덱스를 재설정한다. 완성된 DataFrame은 .rename() 메서드를 사용하여 "sum_07"로 이름이 변경된다.
df_tmp_3 = pd.merge(df_tmp_1, df_tmp_2, how='inner', on='customer_id'): 이 행은 내부 결합을 사용하여 df_tmp_1과 df_tmp_2를 'customer_id' 컬럼으로 병합한다. 이렇게 하면 각 고객의 'sum_all' 컬럼과 'sum_07' 컬럼을 포함하는 새로운 DataFrame이 생성된다.
df_tmp_3['sales_rate'] = df_tmp_3['sum_07'] / df_tmp_3['sum_all']: 이 행은 df_tmp_3의 새로운 열 'sales_rate'를 계산하는 것으로, 'sum_07' 열을 'sum_all' 열로 나눈 결과입니다. 이는 고객의 총 구매액 중 '07' 카테고리에서 구매한 금액의 비율을 계산하는 것이다.
df_tmp_3.head(10): df_tmp_3 DataFrame의 처음 10개의 행을 출력한다. 이 행은 이전 행의 계산이 제대로 이루어졌는지 확인하는 데 사용된다.
# 코드 예시 2 (참고, unstack과 횡방향 sum을 사용한 예시)
df_temp = df_receipt.merge(df_product, how='left', on='product_cd'). \
groupby(['customer_id', 'category_major_cd'])['amount'].sum().unstack()
df_temp = df_temp[df_temp['07'] > 0]
df_temp['sum_all'] = df_temp.sum(axis=1)
df_temp['sales_rate'] = df_temp['07'] / df_temp['sum_all']
# 이후는 데이터 프레임의 정형화 및 표시를 위한 처리
df_temp.columns.name = ''
df_temp = df_temp.reset_index()
df_temp.head(10)
customer_id | 04 | 05 | 06 | 07 | 08 | 09 | sum_all | sales_rate | |
---|---|---|---|---|---|---|---|---|---|
0 | CS001113000004 | NaN | NaN | NaN | 1298.0 | NaN | NaN | 1298.0 | 1.000000 |
1 | CS001114000005 | NaN | 40.0 | NaN | 486.0 | 100.0 | NaN | 626.0 | 0.776358 |
2 | CS001115000010 | NaN | NaN | NaN | 2694.0 | NaN | 350.0 | 3044.0 | 0.885020 |
3 | CS001205000004 | 100.0 | 128.0 | 286.0 | 346.0 | 368.0 | 760.0 | 1988.0 | 0.174044 |
4 | CS001205000006 | 635.0 | 60.0 | 198.0 | 2004.0 | 80.0 | 360.0 | 3337.0 | 0.600539 |
5 | CS001212000027 | 248.0 | NaN | NaN | 200.0 | NaN | NaN | 448.0 | 0.446429 |
6 | CS001212000031 | NaN | NaN | NaN | 296.0 | NaN | NaN | 296.0 | 1.000000 |
7 | CS001212000046 | NaN | NaN | NaN | 108.0 | NaN | 120.0 | 228.0 | 0.473684 |
8 | CS001212000070 | NaN | NaN | 148.0 | 308.0 | NaN | NaN | 456.0 | 0.675439 |
9 | CS001213000018 | NaN | NaN | NaN | 145.0 | 98.0 | NaN | 243.0 | 0.596708 |
해설:
이 코드에서는 pandas의 DataFrame 객체인 df_receipt와 df_product도 조작하고 있습니다.
아래 코드를 한 줄씩 분해해 보겠습니다.
df_temp = df_receipt.merge(df_product, how='left', on='product_cd').groupby(['customer_id', 'category_major_cd']) ['amount'].sum(). unstack(): 이 행은 먼저 왼쪽 결합을 사용하여 df_receipt와 df_product를 "product_cd" 컬럼으로 결합한다. 그런 다음 결과 DataFrame을 "customer_id"와 "category_major_cd"로 그룹화하여 각 그룹의 "amount" 열의 합계를 계산하고, 결과 시리즈를 언스택하여 "customer_id"를 인덱스로, "category_major_cd"를 열 이름으로 지정합니다. category_major_cd", "amount" 열을 값으로 하는 DataFrame을 만들었습니다.
df_temp = df_temp[df_temp['07'] > 0]: 이 행은 df_temp DataFrame을 필터링하여 '07' 열의 값이 0보다 큰 행만 포함하도록 합니다. 이렇게 하면 '07' 카테고리에서 아무것도 구매하지 않은 고객이 제거됩니다.
df_temp['sum_all'] = df_temp.sum(axis=1): 이 행은 df_temp에 'sum_all'이라는 새로운 열을 계산하여 각 행의 모든 열의 합계를 계산합니다. 이는 각 고객이 모든 카테고리에서 지출한 금액의 합계를 계산하는 것이다.
df_temp['sales_rate'] = df_temp['07'] / df_temp['sum_all']: 이 행은 df_temp에 'sales_rate'라는 새로운 열을 계산한다. 이는 고객의 총 구매 금액 중 '07' 카테고리에서 구매한 금액의 비율을 계산하는 것입니다.
df_temp.columns.name = '': 이 행은 df_temp DataFrame에서 열 이름을 삭제하고 있습니다.
df_temp = df_temp.reset_index(): 이 행은 df_temp의 인덱스를 재설정하고 0부터 시작하는 정수 범위를 가진 'index'라는 새로운 열을 생성합니다.
df_temp.head(10): df_temp 데이터 프레임의 처음 10개의 행을 출력한다. 이 행은 이전 행의 계산이 올바르게 수행되었는지 확인하는 데 사용됩니다.
P-070: 영수증 내역 데이터(df_receipt)의 매출일(sales_ymd)에 대해 고객 데이터(df_customer)의 회원가입일(application_date)로부터의 경과일수를 계산하여 고객 ID(customer_id), 매출일, 회원가입일과 함께 10건을 표시하라(sales_ymd는 수치, application_date는 문자열로 데이터를 보관하고 있다는 점에 유의).
df_tmp = df_receipt[['customer_id', 'sales_ymd']].drop_duplicates()
df_tmp = pd.merge(df_tmp, df_customer[['customer_id', 'application_date']],
how='inner', on='customer_id')
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date'])
df_tmp['elapsed_days'] = df_tmp['sales_ymd'] - df_tmp['application_date']
df_tmp['elapsed_days'] = df_tmp['elapsed_days'].dt.days
df_tmp.head(10)
customer_id | sales_ymd | application_date | elapsed_days | |
---|---|---|---|---|
0 | CS006214000001 | 2018-11-03 | 2015-02-01 | 1371 |
1 | CS006214000001 | 2017-05-09 | 2015-02-01 | 828 |
2 | CS006214000001 | 2017-06-08 | 2015-02-01 | 858 |
3 | CS006214000001 | 2018-10-28 | 2015-02-01 | 1365 |
4 | CS006214000001 | 2019-09-08 | 2015-02-01 | 1680 |
5 | CS006214000001 | 2018-01-31 | 2015-02-01 | 1095 |
6 | CS006214000001 | 2017-07-05 | 2015-02-01 | 885 |
7 | CS006214000001 | 2018-11-10 | 2015-02-01 | 1378 |
8 | CS006214000001 | 2019-04-10 | 2015-02-01 | 1529 |
9 | CS006214000001 | 2019-06-01 | 2015-02-01 | 1581 |
해설:
이 코드는 df_receipt라는 DataFrame과 df_customer라는 또 다른 DataFrame에 대해 데이터 조작을 하고 있습니다. 고객의 신청일과 구매일 사이의 일수를 계산하고 있습니다.
다음은 코드의 단계별 예시입니다.
df_tmp = df_receipt[['customer_id', 'sales_ymd']].drop_duplicates(): df_receipt DataFrame에서 "customer_id"와 "sales_ymd" 컬럼만 선택해서 df_tmp라는 새로운 DataFrame을 생성하고 중복된 행을 삭제합니다. 그 결과, 고객 ID와 매출 날짜의 조합이 고유한 DataFrame이 만들어졌다.
df_tmp = pd.merge(df_tmp, df_customer[['customer_id', 'application_date']], how='inner', on='customer_id'): df_tmp와 df_customer DataFrame의 내부 결합을 수행하고, df_customer DataFrame에서 'customer_id' 열과 'application_date' 열만 선택한다. 결과 DataFrame에는 df_tmp와 df_customer에 모두 존재하는 고객 ID를 가진 행만 포함하게 된다.
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str')): df_tmp의 "sales_ymd" 열을 pd.to_datetime() 함수를 사용하여 datetime 형식으로 변환한다. 이 함수는 컬럼을 문자열 형식에서 계산에 사용할 수 있는 datetime 형식으로 변환한다.
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date']): df_tmp의 "application_date" 칼럼을 pd.to_datetime() 함수를 사용하여 datetime 형식으로 변환한다. 로 변환한다. 이 함수는 컬럼을 문자열 형식에서 계산에 사용할 수 있는 datetime 형식으로 변환한다.
df_tmp['elapsed_days'] = df_tmp['sales_ymd'] - df_tmp['application_date']: "sales_ymd"와 "application_date" 열의 차이를 계산하고, 그 결과를 df_tmp의 새로운 열 "elapsed _days"에 대입한다. 이 계산의 결과로 두 날짜의 차이를 일, 시간, 분, 초 단위로 표현한 timedelta 객체가 생성된다.
df_tmp['elapsed_days'] = df_tmp['elapsed_days'].dt.days: "elapsed_days" 열에서 일수만 가져와서 그 결과를 같은 열에 대입하여 반환한다. 객체가 포함되어 있지만, 일수만 보관하고 싶기 때문에 이 단계가 필요하다.
df_tmp.head(10): df_tmp의 처음 10줄을 표시하여 계산이 제대로 이루어졌는지 확인한다.
전반적으로 이 코드는 고객의 행동을 분석하고 고객이 멤버십이나 계정을 신청한 후 구매하기까지 걸리는 시간을 파악하는 데 유용하다.
P-071: 영수증 내역 데이터(df_receipt)의 매출일(sales_ymd)에 대해 고객 데이터(df_customer)의 회원가입일(application_date)로부터의 경과 개월 수를 계산하여 고객 ID(customer_id), 매출일, 회원가입일과 함께 10건 표시 (sales_ymd는 숫자, application_date는 문자열로 데이터를 보관하는 점에 유의) 1개월 미만은 반올림한다.
df_tmp = df_receipt[['customer_id', 'sales_ymd']].drop_duplicates()
df_tmp = pd.merge(df_tmp, df_customer[['customer_id', 'application_date']],
how='inner', on='customer_id')
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date'])
df_tmp['elapsed_months'] = df_tmp[['sales_ymd', 'application_date']]. \
apply(lambda x: relativedelta(x[0], x[1]).years * 12 + \
relativedelta(x[0], x[1]).months, axis=1)
df_tmp.head(10)
customer_id | sales_ymd | application_date | elapsed_months | |
---|---|---|---|---|
0 | CS006214000001 | 2018-11-03 | 2015-02-01 | 45 |
1 | CS006214000001 | 2017-05-09 | 2015-02-01 | 27 |
2 | CS006214000001 | 2017-06-08 | 2015-02-01 | 28 |
3 | CS006214000001 | 2018-10-28 | 2015-02-01 | 44 |
4 | CS006214000001 | 2019-09-08 | 2015-02-01 | 55 |
5 | CS006214000001 | 2018-01-31 | 2015-02-01 | 35 |
6 | CS006214000001 | 2017-07-05 | 2015-02-01 | 29 |
7 | CS006214000001 | 2018-11-10 | 2015-02-01 | 45 |
8 | CS006214000001 | 2019-04-10 | 2015-02-01 | 50 |
9 | CS006214000001 | 2019-06-01 | 2015-02-01 | 52 |
해설:
이 코드는 지난번 설명한 코드와 비슷하지만, 고객의 신청일로부터 구매일까지의 일수를 계산하는 것이 아니라 경과한 개월 수를 계산하는 코드입니다.
다음은 코드를 단계별로 설명합니다.
df_tmp = df_receipt[['customer_id', 'sales_ymd']].drop_duplicates(): df_receipt DataFrame에서 customer_id와 sales_ymd 열만 선택하고 중복된 행을 삭제하여 새로운 df_tmp라는 DataFrame을 생성합니다. 이렇게 하면 고객 ID와 판매 날짜의 조합이 고유한 DataFrame이 만들어집니다.
df_tmp = pd.merge(df_tmp, df_customer[['customer_id', 'application_date']], how='inner', on='customer_id'): df_tmp와 df_customer DataFrame의 내부 결합을 수행하고, df_customer DataFrame에서 'customer_id' 열과 'application_date' 열만 선택한다. 결과 DataFrame에는 df_tmp와 df_customer에 모두 존재하는 고객 ID를 가진 행만 포함하게 된다.
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str')): df_tmp의 "sales_ymd" 열을 pd.to_datetime() 함수를 사용하여 datetime 형식으로 변환한다.
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date']): df_tmp의 "application_date" 컬럼을 pd.to_datetime() 함수를 사용하여 datetime 형식으로 변환한다.
df_tmp['elapsed_months'] = df_tmp[['sales_ymd', 'application_date']]. \ Γ apply(lambda x: relativedelta(x[0], x[1]).years * 12 + Γ relativedelta(x[0], x[1]).months, axis=1): "sales_ymd"와 "application_date" 열의 차이를 계산한다, 결과를 df_tmp의 "elapsed_months"라는 새로운 열에 대입한다. 이 계산은 dateutil 라이브러리의 relativedelta 함수를 사용하여 두 datetime 값의 차이를 년 또는 월 단위로 계산하고, apply() 메서드에서 "sales_ymd"와 "application_date" 열의 각 행에 이 함수를 적용하고 경과한 개월의 합계를 반환한다.
df_tmp.head(10): df_tmp의 처음 10개의 행을 표시하여 계산이 제대로 이루어졌는지 확인한다.
전체적으로 이 코드는 고객의 행동을 분석하고 고객이 회원 가입 또는 계정을 신청한 후 구매하기까지의 기간을 경과 개월 수로 판단하는 데 편리합니다.
P-072: 영수증 내역 데이터(df_receipt)의 매출일(df_customer)에 대해 고객 데이터(df_customer)의 회원가입 신청일(application_date)로부터의 경과년수를 계산하여 고객 ID(customer_id), 매출일, 회원가입 신청일과 함께 10건 (sales_ymd는 수치, application_date는 문자열로 데이터를 보관하고 있다는 점에 유의) 1년 미만은 반올림한다.
df_tmp = df_receipt[['customer_id', 'sales_ymd']].drop_duplicates()
df_tmp = pd.merge(df_tmp, df_customer[['customer_id', 'application_date']],
how='inner', on='customer_id')
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date'])
df_tmp['elapsed_years'] = df_tmp[['sales_ymd', 'application_date']]. \
apply(lambda x: relativedelta(x[0], x[1]).years, axis=1)
df_tmp.head(10)
customer_id | sales_ymd | application_date | elapsed_years | |
---|---|---|---|---|
0 | CS006214000001 | 2018-11-03 | 2015-02-01 | 3 |
1 | CS006214000001 | 2017-05-09 | 2015-02-01 | 2 |
2 | CS006214000001 | 2017-06-08 | 2015-02-01 | 2 |
3 | CS006214000001 | 2018-10-28 | 2015-02-01 | 3 |
4 | CS006214000001 | 2019-09-08 | 2015-02-01 | 4 |
5 | CS006214000001 | 2018-01-31 | 2015-02-01 | 2 |
6 | CS006214000001 | 2017-07-05 | 2015-02-01 | 2 |
7 | CS006214000001 | 2018-11-10 | 2015-02-01 | 3 |
8 | CS006214000001 | 2019-04-10 | 2015-02-01 | 4 |
9 | CS006214000001 | 2019-06-01 | 2015-02-01 | 4 |
해설:
이 코드는 앞서 설명한 예제와 비슷하지만, 고객의 신청일로부터 구매일까지의 경과 일수나 경과 월수를 계산하는 것이 아니라 경과 년수를 계산하는 코드입니다.
아래 코드를 순서대로 설명합니다.
df_tmp = df_receipt[['customer_id', 'sales_ymd']].drop_duplicates(): df_receipt DataFrame에서 customer_id와 sales_ymd라는 컬럼만 선택하여 중복되는 행을 모두 삭제하고 하여 df_tmp라는 새로운 데이터 프레임을 생성합니다. 이렇게 하면 고객 ID와 판매일 조합이 고유한 DataFrame이 만들어진다.
df_tmp = pd.merge(df_tmp, df_customer[['customer_id', 'application_date']], how='inner', on='customer_id'): df_tmp와 df_customer DataFrame의 내부 결합을 수행하고, df_customer DataFrame에서 'customer_id' 열과 'application_date' 열만 선택한다. 결과 DataFrame에는 df_tmp와 df_customer에 모두 존재하는 고객 ID를 가진 행만 포함하게 된다.
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str')): df_tmp의 "sales_ymd" 열을 pd.to_datetime() 함수를 사용하여 datetime 형식으로 변환한다.
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date']): df_tmp의 "application_date" 컬럼을 pd.to_datetime() 함수를 사용하여 datetime 형식으로 변환한다.
df_tmp['elapsed_years'] = df_tmp[['sales_ymd', 'application_date']]. \ Γ apply(lambda x: relativedelta(x[0], x[1]).years, axis=1): "sales_ymd"와 "application_date" 열의 차이를 계산하고 그 결과를 df_tmp의 새로운 열 "elapsed_years"에 대입한다. 에 대입한다. 이 계산은 dateutil 라이브러리의 relativedelta 함수를 사용하여 두 datetime 값의 차이를 연 단위로 계산하고, apply() 메서드는 "sales_ymd"와 "application_date" 열의 각 행에 이 함수를 적용하여 총 경과 연수를 반환한다.
df_tmp.head(10): df_tmp의 처음 10개의 행을 표시하여 계산이 제대로 이루어졌는지 확인한다.
전체적으로 이 코드는 고객의 행동을 분석하고 고객이 회원 가입이나 계정을 신청한 후 구매하기까지의 기간을 경과 년수로 판단하는 데 유용하다.
P-073: 영수증 내역 데이터(df_receipt)의 매출일(sales_ymd)에 대해 고객 데이터(df_customer)의 회원가입일(application_date)로부터의 에포크 초 단위의 경과 시간을 계산하여 고객 ID(customer_id), 매출일, 회원가입일과 함께 10건을 표시한다. 과 함께 10건을 표시하라(단, sales_ymd는 수치, application_date는 문자열로 데이터를 보유하고 있다는 점에 유의). 단, 시간 정보는 보유하지 않으므로 각 날짜는 0시 0분 0초로 표시한다.
df_tmp = df_receipt[['customer_id', 'sales_ymd']].drop_duplicates()
df_tmp = pd.merge(df_tmp, df_customer[['customer_id', 'application_date']],
how='inner', on='customer_id')
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date'])
df_tmp['elapsed_epoch'] = df_tmp['sales_ymd'].view(np.int64) - \
df_tmp['application_date'].view(np.int64)
df_tmp['elapsed_epoch'] = df_tmp['elapsed_epoch'] / 10**9
df_tmp.head(10)
customer_id | sales_ymd | application_date | elapsed_epoch | |
---|---|---|---|---|
0 | CS006214000001 | 2018-11-03 | 2015-02-01 | 118454400.0 |
1 | CS006214000001 | 2017-05-09 | 2015-02-01 | 71539200.0 |
2 | CS006214000001 | 2017-06-08 | 2015-02-01 | 74131200.0 |
3 | CS006214000001 | 2018-10-28 | 2015-02-01 | 117936000.0 |
4 | CS006214000001 | 2019-09-08 | 2015-02-01 | 145152000.0 |
5 | CS006214000001 | 2018-01-31 | 2015-02-01 | 94608000.0 |
6 | CS006214000001 | 2017-07-05 | 2015-02-01 | 76464000.0 |
7 | CS006214000001 | 2018-11-10 | 2015-02-01 | 119059200.0 |
8 | CS006214000001 | 2019-04-10 | 2015-02-01 | 132105600.0 |
9 | CS006214000001 | 2019-06-01 | 2015-02-01 | 136598400.0 |
해설:
이 코드는 datetime의 값을 유닉스 타임스탬프로 변환하여 고객의 신청일과 구매일 사이의 경과 시간(초)을 계산하는 코드입니다.
다음은 코드의 단계별 예시입니다.
df_tmp = df_receipt[['customer_id', 'sales_ymd']].drop_duplicates(): df_receipt DataFrame에서 "customer_id"와 "sales_ymd" 컬럼만 선택해서 df_tmp라는 새로운 DataFrame을 생성하고, 중복된 행은 삭제합니다. 그 결과 고객 ID와 판매일 조합이 고유한 DataFrame이 만들어졌다.
df_tmp = pd.merge(df_tmp, df_customer[['customer_id', 'application_date']], how='inner', on='customer_id'): df_tmp와 df_customer DataFrame의 내부 결합을 수행하고, df_customer DataFrame에서 'customer_id' 열과 'application_date' 열만 선택한다. 결과 DataFrame에는 df_tmp와 df_customer에 모두 존재하는 고객 ID를 가진 행만 포함하게 된다.
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str')): df_tmp의 "sales_ymd" 열을 pd.to_datetime() 함수를 사용하여 datetime 형식으로 변환한다.
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date']): df_tmp의 "application_date" 컬럼을 pd.to_datetime() 함수를 사용하여 datetime 형식으로 변환한다.
df_tmp['elapsed_epoch'] = df_tmp['sales_ymd'].view(np.int64) - \ df_tmp['application_date'].view(np.int64): "sales_ymd" 와 "application_date" 열의 차이를 계산한다. date" 열의 차이를 계산하고 그 결과를 df_tmp의 "elapsed_epoch" 열에 할당한다. 이 계산은 view() 메서드에서 datetime 값을 int64 형식(Unix 타임스탬프)으로 변환하고, 두 값을 요소별로 감산하여 나노초 단위의 경과 시간을 구하고 있다.
df_tmp['elapsed_epoch'] = df_tmp['elapsed_epoch'] / 10**9: "elapsed_epoch" 열을 10^9로 나누어 경과 시간을 나노초에서 초로 변환한다.
df_tmp.head(10): df_tmp의 처음 10줄을 표시하여 계산이 제대로 이루어졌는지 확인한다.
전체적으로 이 코드는 고객의 행동을 분석하고 고객이 회원 가입이나 계정을 신청한 후 구매하기까지의 시간을 초 단위로 판단하는 데 유용하다.
P-074: 영수증 내역 데이터(df_receipt)의 매출일(sales_ymd)에 대해 해당 주 월요일부터의 경과일수를 계산하여 매출일, 직전 월요일까지 10건씩 표시하라(sales_ymd는 수치로 데이터를 보관하고 있다는 점에 유의).
df_tmp = df_receipt[['sales_ymd']].copy()
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['elapsed_days'] = df_tmp['sales_ymd'].apply(lambda x:x.weekday())
df_tmp['monday'] = \
df_tmp['sales_ymd'].apply(lambda x: x - relativedelta(days=x.weekday()))
df_tmp.head(10)
sales_ymd | elapsed_days | monday | |
---|---|---|---|
0 | 2018-11-03 | 5 | 2018-10-29 |
1 | 2018-11-18 | 6 | 2018-11-12 |
2 | 2017-07-12 | 2 | 2017-07-10 |
3 | 2019-02-05 | 1 | 2019-02-04 |
4 | 2018-08-21 | 1 | 2018-08-20 |
5 | 2019-06-05 | 2 | 2019-06-03 |
6 | 2018-12-05 | 2 | 2018-12-03 |
7 | 2019-09-22 | 6 | 2019-09-16 |
8 | 2017-05-04 | 3 | 2017-05-01 |
9 | 2019-10-10 | 3 | 2019-10-07 |
해설:
이 코드는 다음과 같은 단계를 수행하고 있습니다.
기존 DataFrame df_receipt에서 sales_ymd 컬럼을 선택하고 해당 컬럼의 복사본을 생성하여 df_tmp라는 새로운 DataFrame을 생성하고 있습니다.
그런 다음 df_tmp의 sales_ymd 컬럼은 pd.to_datetime 함수를 사용하여 pandas의 datetime 형식으로 변환됩니다. 이렇게 하면 요일 등의 정보를 추출할 수 있는 등 보다 유연한 방식으로 날짜를 다룰 수 있게 된다.
df_tmp에는 apply 메서드를 사용하여 elapsed_days라는 새로운 컬럼이 생성됩니다. 이 메서드는 sales_ymd 열의 각 행에 람다 함수를 적용하고, 이 경우 요일을 정수 값으로 반환한다(월요일은 0, 일요일은 6이다).
apply 메서드와 또 다른 람다 함수를 사용하여 df_tmp에 new column named monday를 생성한다. 이 함수는 sales_ymd 열의 각 행을 받아 주초부터 경과한 일수(x.weekday()에서 얻은)를 빼고, 그 결과 날짜를 가장 최근 월요일로 반올림하여 반환한다. 이렇게 하면 df_tmp의 모든 날짜가 월요일을 기점으로 한 주 단위로 효과적으로 그룹화됩니다.
마지막으로 head 메서드가 df_tmp에서 호출되어 결과 DataFrame의 처음 10행이 표시됩니다.
전체적으로 이 코드는 판매일 열을 처리하여 분석 또는 집계 목적으로 유용한 정보를 추출하고 있습니다. 구체적으로 DataFrame에 요일과 해당 주의 시작일을 나타내는 새로운 열을 생성하여 주 또는 요일별로 판매 데이터를 그룹화하거나 집계하는 데 사용할 수 있습니다.
P-075: 고객 데이터(df_customer)에서 무작위로 1%의 데이터를 추출하여 맨 앞부터 10개를 표시하라.
df_customer.sample(frac=0.01).head(10)
customer_id | customer_name | gender_cd | gender | birth_day | age | postal_cd | address | application_store_cd | application_date | status_cd | |
---|---|---|---|---|---|---|---|---|---|---|---|
15707 | CS003702000032 | 松山 充則 | 0 | 男性 | 1943-12-24 | 75 | 214-0001 | 神奈川県川崎市多摩区菅********** | S13003 | 20160422 | 0-00000000-0 |
13320 | CS014712000023 | 大山 夏空 | 1 | 女性 | 1940-11-02 | 78 | 264-0024 | 千葉県千葉市若葉区高品町********** | S12014 | 20150630 | 0-00000000-0 |
2995 | CS028402000059 | 山口 寛治 | 0 | 男性 | 1970-06-07 | 48 | 245-0009 | 神奈川県横浜市泉区新橋町********** | S14028 | 20150508 | 0-00000000-0 |
5927 | CS007313000191 | 梅沢 麻緒 | 1 | 女性 | 1984-12-29 | 34 | 285-0858 | 千葉県佐倉市ユーカリが丘********** | S12007 | 20180913 | 0-00000000-0 |
20923 | CS017713000004 | 川瀬 未華子 | 1 | 女性 | 1947-12-12 | 71 | 166-0003 | 東京都杉並区高円寺南********** | S13017 | 20141218 | 0-00000000-0 |
13579 | CS004412000576 | 重松 真帆 | 1 | 女性 | 1970-05-29 | 48 | 167-0051 | 東京都杉並区荻窪********** | S13004 | 20170501 | 0-00000000-0 |
5845 | CS025512000044 | 上村 菜々美 | 1 | 女性 | 1963-03-08 | 56 | 242-0014 | 神奈川県大和市上和田********** | S14025 | 20150222 | 3-20080930-4 |
21563 | CS001412000408 | 古沢 まさみ | 1 | 女性 | 1976-08-13 | 42 | 210-0852 | 神奈川県川崎市川崎区鋼管通********** | S13001 | 20160102 | 0-00000000-0 |
8983 | CS004313000089 | 志水 陽子 | 1 | 女性 | 1983-07-06 | 35 | 176-0014 | 東京都練馬区豊玉南********** | S13004 | 20151010 | 0-00000000-0 |
703 | CS018512000192 | 寺西 郁恵 | 1 | 女性 | 1965-09-29 | 53 | 204-0002 | 東京都清瀬市旭が丘********** | S13018 | 20180528 | 0-00000000-0 |
해설:
이 코드는 df_customer라는 pandas의 DataFrame에서 행의 하위 집합을 샘플링하여 그 하위 집합의 첫 10개의 행을 표시하는 코드입니다.
아래는 코드의 각 부분이 하는 일이다.
df_customer는 고객 데이터가 포함된 pandas의 DataFrame으로, 아마도 많은 행과 열이 있을 것이다.
sample 메서드는 df_customer에 대해 frac=0.01이라는 인수로 호출된다. 이 메서드는 DataFrame의 일부 행을 무작위로 선택한다. 여기서 frac은 샘플링할 행의 비율을 나타낸다. 이 경우 frac=0.01은 df_customer의 행 중 1%가 무작위로 선택됨을 의미한다.
head 메서드는 df_customer 결과의 하위 집합에 대해 인수 10으로 호출된다. 이 메서드는 하위 집합의 처음 10개의 행을 반환하고 이를 표시합니다.
즉, 이 코드는 전체적으로 df_customer에서 1%의 무작위 샘플을 선택하고 그 샘플의 처음 10개의 행을 표시하고 있다. 이는 전체 데이터 세트를 조작하지 않고도 데이터의 작은 하위 집합에 대해 데이터를 탐색하거나 빠른 분석을 수행하는 데 유용하다.
P-076: 고객 데이터(df_customer)에서 성별 코드(gender_cd)의 비율에 따라 무작위로 10%의 데이터를 층화 추출하고, 성별 코드별로 건수를 집계하라.
# sklearn.model_selection.train_test_split 사용 예시
_, df_tmp = train_test_split(df_customer, test_size=0.1,
stratify=df_customer['gender_cd'])
df_tmp.groupby('gender_cd').agg({'customer_id' : 'count'})
customer_id | |
---|---|
gender_cd | |
0 | 298 |
1 | 1793 |
9 | 107 |
해설:
이 코드는 다음 단계를 수행한다.
train_test_split은 scikit-learn 라이브러리의 함수로, 데이터 세트를 무작위로 두 개의 하위 집합('훈련' 하위 집합과 '테스트' 하위 집합)으로 분할하는 함수입니다. 이 코드에서는 df_customer라는 pandas의 DataFrame에 적용하고, 인수 test_size=0.1로 데이터의 10%를 테스트용으로 확보할 것을 지정하고, 인수 stratify=df_customer['gender_cd']로 gender_cd 열의 값으로 계층화할 것을 지정했습니다. 이 함수는 두 개의 DataFrame을 반환하지만, 첫 번째 DataFrame은 무시된다(_가 할당되는데, 이는 파이썬의 관행으로, 사용되지 않을 변수를 나타내는 것이다).
train_test_split이 반환하는 두 번째 DataFrame은 df_tmp라는 새로운 변수에 할당된다.
df_tmp에 대해 gender_cd라는 인수로 groupby 메서드가 호출된다. 이 메서드는 df_tmp의 행을 gender_cd 열의 값으로 그룹화한다.
agg 메서드는 groupby 연산 결과에 대해 {'customer_id' : 'count'}라는 인수로 호출된다. 이 메서드는 각 그룹의 customer_id 컬럼에 함수(여기서는 count)를 적용하고, 각 그룹의 결과 카운트를 가진 DataFrame을 반환한다.
완성된 DataFrame이 표시된다.
즉, 이 코드는 전체적으로 pandas의 DataFrame을 훈련용과 테스트용으로 무작위로 분할하고 특정 열(gender_cd)의 값에 따라 계층화합니다. 그리고 테스트용 서브셋에서 새로운 DataFrame을 생성하여 gender_cd 열로 그룹화하여 각 그룹의 customer_id 열의 고유한 값의 개수를 계산할 수 있습니다. 이는 테스트용 서브셋의 데이터에서 성별에 따른 고객 분포를 파악하는 데 유용합니다.
P-077: 영수증 명세서 데이터(df_receipt)의 매출 금액을 고객 단위로 합산하고, 합산한 매출 금액의 편차를 추출하라. 단, 이상값은 매출금액 합계를 로그화한 후 평균과 표준편차를 계산하여 그 평균에서 3σ 이상 벗어난 것으로 한다(자연대수, 상용대수 모두 가능). 결과는 10건 표시하라.
df_sales_amount = df_receipt.groupby('customer_id'). \
agg({'amount':'sum'}).reset_index()
df_sales_amount['log_sum_amount'] = np.log(df_sales_amount['amount'] + 0.5)
df_sales_amount['log_sum_amount_ss'] = preprocessing.scale(df_sales_amount['log_sum_amount'])
df_sales_amount.query('abs(log_sum_amount_ss) > 3').head(10)
customer_id | amount | log_sum_amount | log_sum_amount_ss | |
---|---|---|---|---|
8306 | ZZ000000000000 | 12395003 | 16.332804 | 7.967822 |
해설:
이 코드는 df_receipt라는 pandas DataFrame 객체에 대해 데이터 전처리 및 필터링을 수행하고 있다.
다음은 코드의 단계별 설명입니다.
df_sales_amount = df_receipt.groupby('customer_id').agg({'amount':'sum'}).reset_index(): 이 행은 df_receipt의 행을 customer_id 열의 값으로 그룹화하고, 각 그룹의 amount 열의 합계를 계산합니다. 그룹의 amount 열의 합계를 계산하고 있다. 결과 DataFrame df_sales_amount는 고유한 customer_id 값마다 1행, customer_id와 amount의 2열을 가지며, reset_index() 메서드를 사용하여 그룹화 후 인덱스를 정수값으로 재설정하여 하고 있습니다.
df_sales_amount['log_sum_amount'] = np.log(df_sales_amount['amount'] + 0.5): 이 행은 df_sales_amount에 log_sum_amount라는 새로운 컬럼을 생성하고, 각 고객별 금액의 합계 의 자연대수, 그리고 0의 대수를 피하기 위해 0.5를 더한 값을 저장합니다.
df_sales_amount['log_sum_amount_ss'] = preprocessing.scale(df_sales_amount['log_sum_amount']): 이 행은 df_sales_amount에 log_sum_amount_ss 라는 새로운 열을 만들고, scikit-learn 라이브러리의 preprocessing.scale 함수를 사용하여 log_sum_amount를 스케일링한 값을 저장한다. 이 함수는 입력 배열의 평균을 빼고 표준편차로 나누어 평균이 0이고 분산이 0인 단위의 새로운 배열을 만든다.
df_sales_amount.query('abs(log_sum_amount_ss) > 3').head(10): 이 행은 log_sum_amount_ss의 절대값이 3보다 큰 행만 남도록 df_sales_amount를 필터링하고 결과의 DataFrame의 처음 10개의 행을 선택한다. 이 필터는 총매출액의 평균값에서 표준편차가 3 이상 떨어져 있는 고객을 선택하여 이상값일 가능성을 표시하고 있으며, query 메서드를 사용하여 문자열 표현식(이 경우 'abs(log_sum_amount_ss) > 3')을 기준으로 행을 필터링하고 있다.
P-078: 영수증 내역 데이터(df_receipt)의 매출 금액(amount)을 고객 단위로 합산하고, 합산된 매출 금액의 외곽값을 추출한다. 단, 고객 ID가 “Z”로 시작하는 것은 비회원을 의미하므로 제외하여 계산한다. 여기서 이상값은 1사분위와 3사분위의 차이인 IQR을 이용하여 ‘1사분위수 -1.5×IQR’ 이하 또는 ‘3사분위수+1.5×IQR’을 초과하는 것으로 한다. 결과는 10건 표시한다.
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python'). \
groupby('customer_id'). \
agg({'amount':'sum'}).reset_index()
pct25 = np.percentile(df_sales_amount['amount'], q=25)
pct75 = np.percentile(df_sales_amount['amount'], q=75)
iqr = pct75 - pct25
amount_low = pct25 - (iqr * 1.5)
amount_hight = pct75 + (iqr * 1.5)
df_sales_amount.query('amount < @amount_low or @amount_hight < amount').head(10)
customer_id | amount | |
---|---|---|
98 | CS001414000048 | 8584 |
332 | CS001605000009 | 18925 |
549 | CS002415000594 | 9568 |
1180 | CS004414000181 | 9584 |
1558 | CS005415000137 | 8734 |
1733 | CS006414000001 | 9156 |
1736 | CS006414000029 | 9179 |
1752 | CS006415000105 | 10042 |
1755 | CS006415000147 | 12723 |
1757 | CS006415000157 | 10648 |
해설:
이 코드는 df_receipt라는 pandas DataFrame 객체에 대해 데이터 전처리 및 필터링을 수행하고 있다.
다음은 코드의 단계별 설명입니다.
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').agg({'amount':'sum '}).reset_index(): 이 행은 먼저 df_receipt를 필터링하여 customer_id 열이 'Z'로 시작하는 행을 제외하고 있으며, query 메서드는 문자열 표현식을 기반으로 행을 필터링하는 데 사용된다. python' 인수는 쿼리에 Python 엔진을 사용하도록 지정하는 데 사용되며, str.startswith 메서드를 사용할 수 있다. 결과 DataFrame은 customer_id로 그룹화되며, 그룹별로 금액 열의 합계가 계산된다. 결과 DataFrame df_sales_amount는 고유한 customer_id 값마다 한 줄, customer_id와 amount 두 개의 컬럼을 가지며, reset_index() 메서드를 사용하여 그룹화 후 인덱스를 정수 값으로 재설정 하고 있다.
pct25 = np.percentile(df_sales_amount['amount'], q=25): 이 행은 numpy.percentile 함수를 사용하여 df_sales_amount의 amount 컬럼의 25번째 백분위수를 계산하고, 그 결과를 변수 pct25에 대입하고 있습니다.
pct75 = np.percentile(df_sales_amount['amount'], q=75): 이 행은 numpy.percentile 함수를 사용하여 df_sales_amount의 amount 열의 75번째 백분위수를 계산하고 그 결과를 pct75 변수에 대입한다. 변수에 대입합니다.
iqr = pct75 - pct25: 이 행은 75백분위수(pct75)에서 25백분위수(pct25)를 빼서 df_sales_amount의 금액 열의 사분위수 범위(IQR)를 계산하고 그 결과를 iqr 변수에 대입하고 있다.
amount_low = pct25 - (iqr * 1.5): 이 행은 amount_low = pct25 - (iqr * 1.5) 공식을 사용하여 판매 금액의 "정상" 범위의 하한을 계산한다. 이것은 일반적으로 박스형 플롯의 아래쪽 "수염"으로 알려져 있다.
amount_high = pct75 + (iqr * 1.5). 이 행은 amount_high = pct75 + (iqr * 1.5) 공식을 사용하여 판매 금액의 "정상" 범위의 상한을 계산한다. 이것은 일반적으로 상자 수염 도표의 상한 "수염"으로 알려져 있다.
df_sales_amount.query('amount < @amount_low or @amount_high < amount').head(10): 이 행은 amount 열이 amount_low보다 작거나 amount_high보다 큰 행만 남겨두도록 df sales_amount를 필터링하여 결과 DataFrame의 처음 10개의 행을 선택한다. 이 필터는 매출 금액 합계가 매출 금액의 '정상' 범위를 벗어난 고객을 선택하는 것으로, 박스 플롯의 아래쪽 수염과 위쪽 수염 사이의 범위로 정의되며, Python의 변수 amount_low와 amount_high를 참조하기 위해 @ 기호를 사용하는 문자열 표현식을 기반으로 한다. 행을 필터링하기 위해 query 메서드가 사용되었습니다.
P-079: 상품 데이터(df_product)의 각 항목에 대해 결손 수를 확인하라.
df_product.isnull().sum()
product_cd 0 category_major_cd 0 category_medium_cd 0 category_small_cd 0 unit_price 7 unit_cost 7 dtype: int64
해설:
이 코드는 pandas의 DataFrame df_product의 각 열의 결손(null) 값 수를 계산하는 데 사용됩니다. 아래에서 단계별로 설명한다.
df_product: 사용 중인 DataFrame의 이름입니다.
isnull(): 이 메서드는 df_product와 동일한 모양의 DataFrame을 반환하며, 각 요소는 df_product의 해당 요소가 null인지 여부를 나타내는 boolean 값이다.
sum(): isnull()이 반환하는 boolean DataFrame에 적용되어 각 열의 boolean 값의 합을 반환하는 메서드이며, boolean 값은 정수로 취급되므로 사실상 df_product의 각 열의 null 값의 개수를 세는 것과 같다.
따라서 df_product.isnull().sum()은 인덱스가 df_product의 열 이름, 값이 각 열의 NULL 값 수인 Series 객체를 반환한다. 이는 누락된 값이 있는 열과 각 열에 몇 개의 누락된 값이 있는지를 파악하는 데 사용할 수 있다.
P-080: 상품 데이터(df_product) 중 어느 한 항목에 결손이 발생한 레코드를 모두 삭제한 새로운 상품 데이터를 생성한다. 또한, 삭제 전후의 건수를 표시하고, 079에서 확인한 건수만큼 감소한 것도 확인해야 한다.
df_product_1 = df_product.copy()
df_product_1.dropna(inplace=True)
print('삭제전:', len(df_product))
print('삭제후:', len(df_product_1))
삭제전: 10030 삭제후: 10023
해설:
이 코드는 pandas의 DataFrame df_product에서 NULL(결손) 값을 포함하는 모든 행을 제거하고, NULL이 아닌 행만 포함하는 새로운 DataFrame df_product_1을 생성하는 데 사용됩니다. 아래에서 단계별로 설명한다.
df_product: 사용할 원본 DataFrame의 이름이다.
df_product.copy(): 이 메서드는 원본 DataFrame의 복사본을 생성하여 원본 DataFrame이 변경되지 않도록 한다.
df_product_1: 생성되는 새로운 DataFrame의 이름이다.
dropna(). 이 메서드는 df_product_1에서 null 값을 포함한 모든 행을 삭제하는 데 사용되며, inplace=True 인수는 새로운 DataFrame을 반환하는 대신 df_product_1을 그 자리에서 변경하는 데 사용된다.
print('Before deletion:', len(df_product)): null 값을 삭제하기 전 원래 DataFrame df_product의 행 수를 표시한다.
print('After deletion:', len(df_product_1)): 이 줄은 널 값이 삭제된 후 새로운 DataFrame df_product_1의 행 수를 표시합니다.
따라서 이 코드에서는 먼저 원본 DataFrame df_product의 복사본을 만들고, 복사한 DataFrame에서 NULL 값을 포함한 모든 행을 삭제하여 새로운 DataFrame df_product_1을 만들고, 마지막으로 NULL 값 삭제 전과 삭제 후의 원본 DataFrame과 새로운 DataFrame의 행 수를 표시합니다. DataFrame과 새로운 DataFrame 내의 행 수를 표시한다. 이 코드는 NULL 값으로 인해 삭제된 행 수를 확인하고 NULL 값을 포함하지 않는 깨끗한 DataFrame을 생성하는 데 사용할 수 있다.
Comment