오늘은 어제 못 한 대분류, 소분류 컬럼 추가하기, 도매/소매 분류하기, 비싼 금액을 어떻게 처리할지에 대한 기준 세우기를 하고자 한다.
어제 Item Class에서 P01이 99.9%를 차지해서 이렇게 하나로 분류되는 Item Class는 유의미하지 않다고 판단해 컬럼 자체를 드랍했었다.
오늘은 Item을 나눠 분석할 수 있도록 대분류, 소분류를 추가하고자 한다.
df = df.reset_index(drop=True)
last = []
for i in range(len(df)):
last.append(df['Item'][i].split(" ")[-1])
df['Food'] = last
# 집합을 통해서 단어 중복 제거 -> 총 116개
last = set(last)
1. 유제품 및 계란류
2. 고기류 및 해산물
3. 과일 및 견과류
4. 채소류
5. 빵, 면, 곡물류
6. 음료
7. 간식 및 디저트
8. 조미료 및 양념류
9. 가공 식품
# 대분류 및 소분류를 매핑하는 딕셔너리
category_map = {
'Almonds': ('과일 및 견과류', '견과류'),
'Anchovies': ('고기류 및 해산물', '해산물'),
'Apples': ('과일 및 견과류', '과일'),
'Apricots': ('과일 및 견과류', '과일'),
'Asparagus': ('채소류', '채소'),
'Bagels': ('빵, 면, 곡물류', '빵 및 베이커리'),
'Balls': ('간식 및 디저트', '과자 및 스낵류'),
'Bar': ('간식 및 디저트', '과자 및 스낵류'),
'Beans': ('채소류', '채소'),
'Beef': ('고기류 및 해산물', '육류'),
'Beer': ('음료', '알코올 음료'),
'Beets': ('채소류', '채소'),
'Bologna': ('고기류 및 해산물', '가공육'),
'Bread': ('빵, 면, 곡물류', '빵 및 베이커리'),
'Broccoli': ('채소류', '채소'),
'Brownies': ('빵, 면, 곡물류', '빵 및 베이커리'),
'Butter': ('유제품 및 계란류', '유제품'),
'Buttermilk': ('유제품 및 계란류', '유제품'),
'Cantelope': ('과일 및 견과류', '과일'),
'Carrots': ('채소류', '채소'),
'Cauliflower': ('채소류', '채소'),
'Chardonnay': ('음료', '알코올 음료'),
'Cheese': ('유제품 및 계란류', '유제품'),
'Chicken': ('고기류 및 해산물', '육류'),
'Chips': ('간식 및 디저트', '과자 및 스낵류'),
'Chocolate': ('간식 및 디저트', '초콜릿 및 캔디'),
'Clams': ('고기류 및 해산물', '해산물'),
'Cob': ('채소류', '채소'),
'Coffee': ('음료', '커피 및 차'),
'Cola': ('음료', '탄산 음료'),
'Cookies': ('간식 및 디저트', '과자 및 스낵류'),
'Corn': ('채소류', '채소'),
'Crackers': ('간식 및 디저트', '과자 및 스낵류'),
'Cream': ('유제품 및 계란류', '유제품'),
'Dates': ('과일 및 견과류', '과일'),
'Dew': ('과일 및 견과류', '과일'),
'Dinner': ('가공 식품', '냉동식품'),
'Dip': ('조미료 및 양념류', '소스 및 드레싱'),
'Dogs': ('가공 식품', '냉동식품'),
'Donuts': ('빵, 면, 곡물류', '빵 및 베이커리'),
'Drink': ('음료', '주스'),
'Eggs': ('유제품 및 계란류', '계란'),
'Fries': ('간식 및 디저트', '과자 및 스낵류'),
'Fruit': ('과일 및 견과류', '과일'),
'Garlic': ('채소류', '채소'),
'Grits': ('가공 식품', '수프'),
'Gum': ('간식 및 디저트', '초콜릿 및 캔디'),
'Ham': ('고기류 및 해산물', '가공육'),
'Hamburger': ('가공 식품', '냉동식품'),
'Jam': ('조미료 및 양념류', '잼'),
'Jelly': ('간식 및 디저트', '초콜릿 및 캔디'),
'Jerky': ('고기류 및 해산물', '가공육'),
'Juice': ('음료', '주스'),
'Lemons': ('과일 및 견과류', '과일'),
'Lettuce': ('채소류', '채소'),
'Limes': ('과일 및 견과류', '과일'),
'Loaf': ('고기류 및 해산물', '가공육'),
'Lox': ('고기류 및 해산물', '해산물'),
'Manicotti': ('빵, 면, 곡물류', '파스타 및 면류'),
'Medly': ('가공 식품', '냉동식품'),
'Milk': ('유제품 및 계란류', '유제품'),
'Mints': ('간식 및 디저트', '초콜릿 및 캔디'),
'Mix': ('가공 식품', '믹스가루'),
'Muffins': ('빵, 면, 곡물류', '빵 및 베이커리'),
'Mushrooms': ('채소류', '채소'),
'Nuts': ('과일 및 견과류', '견과류'),
'Oatmeal': ('빵, 면, 곡물류', '곡류'),
'Oil': ('조미료 및 양념류', '버터 및 오일'),
'Onion': ('채소류', '채소'),
'Onions': ('채소류', '채소'),
'Oranges': ('과일 및 견과류', '과일'),
'Oregano': ('채소류', '채소'),
'Oysters': ('고기류 및 해산물', '해산물'),
'Pancakes': ('가공 식품', '냉동식품'),
'Peaches': ('과일 및 견과류', '과일'),
'Peanuts': ('과일 및 견과류', '견과류'),
'Peas': ('채소류', '채소'),
'Pepper': ('조미료 및 양념류', '양념 및 향신료'),
'Pizza': ('가공 식품', '냉동식품'),
'Plums': ('과일 및 견과류', '과일'),
'Popcorn': ('간식 및 디저트', '과자 및 스낵류'),
'Popsicles': ('유제품 및 계란류', '유제품'),
'Potatos': ('채소류', '채소'),
'Preserves': ('조미료 및 양념류', '잼'),
'Pretzels': ('간식 및 디저트', '과자 및 스낵류'),
'Puffs': ('빵, 면, 곡물류', '빵 및 베이커리'),
'Raisins': ('과일 및 견과류', '과일'),
'Ravioli': ('빵, 면, 곡물류', '파스타 및 면류'),
'Rice': ('빵, 면, 곡물류', '곡류'),
'Roll': ('간식 및 디저트', '과자 및 스낵류'),
'Salad': ('채소류', '채소'),
'Salt': ('조미료 및 양념류', '양념 및 향신료'),
'Sandwich': ('유제품 및 계란류', '유제품'),
'Sardines': ('고기류 및 해산물', '해산물'),
'Sauce': ('조미료 및 양념류', '소스 및 드레싱'),
'Scallops': ('고기류 및 해산물', '해산물'),
'Shrimp': ('고기류 및 해산물', '해산물'),
'Slaw': ('채소류', '채소'),
'Soda': ('음료', '탄산 음료'),
'Soup': ('가공 식품', '수프'),
'Spaghetti': ('빵, 면, 곡물류', '파스타 및 면류'),
'Spread': ('조미료 및 양념류', '소스 및 드레싱'),
'Squash': ('채소류', '채소'),
'Sugar': ('조미료 및 양념류', '양념 및 향신료'),
'Tangerines': ('과일 및 견과류', '과일'),
'Thighs': ('고기류 및 해산물', '육류'),
'Tofu': ('채소류', '채소'),
'Tomatos': ('채소류', '채소'),
'Turkey': ('고기류 및 해산물', '육류'),
'Waffles': ('빵, 면, 곡물류', '빵 및 베이커리'),
'Walnuts': ('과일 및 견과류', '견과류'),
'Water': ('가공 식품', '캔'),
'Wine': ('음료', '알코올 음료'),
'Wings': ('고기류 및 해산물', '육류'),
'Yams': ('가공 식품', '캔'),
'Yogurt': ('유제품 및 계란류', '유제품')
}
# 대분류와 소분류를 할당하는 함수
def assign_category(food_item):
return category_map.get(food_item, ('Uncategorized', 'Uncategorized'))
# 음식 리스트를 데이터프레임으로 변환
category = pd.DataFrame(last, columns=['Food'])
# 대분류와 소분류 컬럼을 추가
category[['Main Category', 'Sub Category']] = category['Food'].apply(lambda x: pd.Series(assign_category(x)))
# 결과 출력
category
# 'Food' 컬럼을 기준으로 원래 df와 category 데이터프레임 병합
df = pd.merge(df, category, on='Food', how='left')
그 다음은 도매/소매 나누기. 아마존 데이터가 도매/소매로 분류되는게 아니라 사실 모두 도매 전자상거래. 그래서 대량으로 살수록 개당 가격이 저렴해지는 특성 때문에 Discount Amount가 모든 행마다 있는 것이 아닐까? 이렇게 생각해서 도매/소매로 처음에는 나누려고 했다. 그래서 평균 * 1.3 을 임계치로 설정하여 그 이하는 소매, 반대는 도매로 설정했었다.
그리고, Imagine Pancake Mix처럼 제품 하나의 정가가 몇 백만원씩 하는 제품들이 꽤 있다. 이런 제품들에 대해서 어떻게 처리하면 좋을지 고민이 됐다.
이 두가지에 대해 아무리 고민해도 명확한 해결책이 떠오르지 않아서 튜터님께 질문을 드렸고 다음과 같은 답변을 받았다.
그래서 결론은 :
amount_threshold = df['Sales Amount'].mean() * 1.3 # 매출 금액 기준 (Sales Amount의 평균 2744.115588)
# 두 가지 기준을 함께 고려하여 소매와 도매로 구분
df['Sales Type'] = df.apply(lambda row: '소량판매' if (row['Sales Amount'] <= amount_threshold) else '대량판매', axis=1)
df['Sales Type'].value_counts()
Sales Type
소량판매 51462
대량판매 5883
Name: count, dtype: int64
# 그래프의 크기 설정
plt.figure(figsize=(12, 8))
# scatterplot 그리기
sns.scatterplot(data=df, x='Sales Amount', y='Sales Quantity', hue='Sales Type', palette={'대량판매': 'blue', '소량판매': 'green'}, alpha=0.6)
# 그래프 제목 및 축 라벨 설정
plt.title('Scatterplot of Sales Quantity vs. Sales Price')
plt.xlabel('Sales Amount')
plt.ylabel('Sales Quantity')
# 범례 추가
plt.legend(title='Sales Type')
# 그래프 표시
plt.show()
결과:
예를 들어 Imagine Pancake Mix 처럼 1 개 단위의 정가가 몇 백만원인 제품들이 많이 있다. 상식적으로 팬 케이크 반죽이 몇 백만원이나 하는 것은 옳지 못하다고 생각.
그래서, 이런 제품들에 대해 기준을 세워 그 기준을 넘는 제품들은 Item 을 살펴보며 합당하면 개별 단위로 보고, 그게 아니라면 Box 단위로 보고자 한다.
# List Price의 평균 계산
list_price_q3 = df['List Price'].quantile(0.75)
# 판매 유형을 구분
def categorize_sale(row):
# Sub Category가 '육류', '해산물', '알코올'인 경우 무조건 개별판매
if row['Sub Category'] in ['육류', '해산물', '알코올 음료']:
return '개별판매'
# List Price가 평균 이상인 경우 박스판매, 그 외에는 개별판매
elif row['List Price'] >= list_price_q3:
return '박스판매'
else:
return '개별판매'
# 새로운 컬럼 '판매 유형' 추가
df['Pricing Category'] = df.apply(categorize_sale, axis=1)
df['Pricing Category'].value_counts()
Pricing Category
개별판매 46349
박스판매 10996
Name: count, dtype: int64
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 57345 entries, 0 to 57344
Data columns (total 24 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Custkey 57345 non-null int64
1 DateKey 57345 non-null datetime64[ns]
2 Discount Amount 57345 non-null float64
3 Invoice Date 57345 non-null datetime64[ns]
4 Invoice Number 57345 non-null int64
5 Item Number 57345 non-null object
6 Item 57345 non-null object
7 Line Number 57345 non-null int64
8 List Price 57345 non-null float64
9 Order Number 57345 non-null int64
10 Promised Delivery Date 57345 non-null datetime64[ns]
11 Sales Amount 57345 non-null float64
12 Sales Amount Based on List Price 57345 non-null float64
13 Sales Cost Amount 57345 non-null float64
14 Sales Margin Amount 57345 non-null float64
15 Sales Price 57345 non-null float64
16 Sales Quantity 57345 non-null int64
17 Sales Rep 57345 non-null int64
18 U/M 57345 non-null object
19 Food 57345 non-null object
...
22 Sales Type 57345 non-null object
23 Pricing Category 57345 non-null object
dtypes: datetime64[ns](3), float64(7), int64(6), object(8)
memory usage: 10.5+ MB
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
그리고 오늘 추가적으로 데이터의 정합성에 대해 고민하던 중 Sales Margin Amount가 음수인 데이터, Sales Cost Amount가 0인 데이터도 찾게 되어 이런 데이터들은 분석의 방향성에 맞지 않다고 생각되어 데이터에서 제외해줬다.