Home 입출고, 반품 데이터
Post
Cancel

입출고, 반품 데이터

Excel 데이터 구조

  • 시트 목록: info, date, 출고, 입고, 반품
  • 특징 정리
    • idx가 같으면 fruit, date도 같다.
    • idx, fruit가 같아도 location은 다를 수 있다.
    • type은 입고, 출고, 미출고, 반품이 있다.
    • date는 입고 날짜를 의미
    • 중복 데이터 가능



1
2
3
4
5
6
import pandas as pd
info = pd.read_excel('test.xlsx', sheet_name='info')
date = pd.read_excel('test.xlsx', sheet_name='date')
df_출고 = pd.read_excel('test.xlsx', sheet_name='출고')
df_입고 = pd.read_excel('test.xlsx', sheet_name='입고')
df_반품 = pd.read_excel('test.xlsx', sheet_name='반품')





작성일자 변경

1
date
index연월작성일
01111-11-011111-01-18



1
2
date['연월'] = '2025-06-01'
date['작성일'] = '2025-07-18'



1
date
index연월작성일
02025-06-012025-07-18





출고

idx 값이 비어있으면 info 시트에서 값 찾아 넣기

1
df_출고.info()
1
2
3
4
5
6
7
8
9
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 87 entries, 0 to 86
Data columns (total 4 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   idx     77 non-null     object
 1   fruit   87 non-null     object
 2   date    87 non-null     int64 
 3   type    87 non-null     object

10개의 idx값이 없다.



1
2
a = info.set_index('fruit')['idx'].to_dict()
a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{'Apple': '02_Ap',
 'Strawberry': '03_St',
 'Lemon': '04_Le',
 'Mango': '05_Ma',
 'Grape': '06_Gr',
 'Orange': '07_Or',
 'Kiwi': '08_Ki',
 'Pineapple': '09_Pi',
 'Melon\xa0': '10_Me',
 'Peach\xa0': '11_Pe',
 'Watermelon\xa0': '12_Wa',
 'Guava': '25_Gu',
 'Pear': '14_Pe',
 'Banana': '15_Ba',
 'Blueberry': '16_Bl',
 'Coconut': '17_Co',
 'Cherry': '18_Ch',
 'Grapefruit': '19_Gr',
 'Durian': '20_Du',
 'Mangosteen': '21_Ma',
 'Apricot': '22_Ap',
 'Lychee': '23_Ly'} 



1
df_출고[df_출고['idx'].isnull()]
indexidxfruitdatetype
6NaNCoconut250717출고
9NaNMangosteen250721출고
14NaNGrapefruit250719출고
24NaNMango250705출고
29NaNKiwi250708출고
40NaNGrapefruit250719출고
46NaNKiwi250708출고
61NaNBlueberry250716출고
83NaNPear250714출고
84NaNBanana250715출고



1
df_출고['idx'] = df_출고['idx'].fillna(df_출고['fruit'].map(a)) 


1
df_출고[df_출고['idx'].isnull()]
indexidxfruitdatetype
     




1
df_출고[df_출고['type']=='미출고']
indexidxfruitdatetype
1115_BaBanana250715미출고
4319_GrGrapefruit250719미출고
5711_PePeach 250711미출고
7009_PiPineapple250709미출고
8618_ChCherry250718미출고



1
df_출고.loc[df_출고['type'] == '미출고', 'idx'] = '-'

*처음에 df_출고[df_출고['type']=='미출고']['idx']='-'로 작성했었다.
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead 라고 뜬다.



1
df_출고[df_출고['type']=='미출고']
indexidxfruitdatetype
11-Banana250715미출고
43-Grapefruit250719미출고
57-Peach 250711미출고
70-Pineapple250709미출고
86-Cherry250718미출고





입고

type이 반품인 경우 returns 값이 비어있을 경우 [반품] 에서 찾아 입력

1
df_입고[(df_입고['type'] == '반품') & (df_입고['returns'].isna())]
indexidxfruitdatereturnstype
408_KiKiwi250708NaN반품
1225_GuGuava250713NaN반품
2015_BaBanana250715NaN반품
3712_WaWatermelon 250712NaN반품
5618_ChCherry250718NaN반품
5815_BaBanana250715NaN반품
7814_PePear250714NaN반품
8016_BlBlueberry250716NaN반품
10212_WaWatermelon 250712NaN반품



1
2
3
lo = df_반품.groupby(['fruit','location']).size().unstack(fill_value=0)
lo['count'] = lo.sum(axis=1)
lo
fruit1호점2호점3호점4호점5호점count
Apple100012
Apricot000011
Banana110114
Blueberry010203
Cherry110114
Durian121004
Grape101103
Grapefruit000101
Guava000246
Kiwi000202
Lychee200002
Mango000011
Melon 000202
Orange001124
Peach 011013
Pear101002
Strawberry000011
Watermelon 000033



참고) Counter

각 원소가 몇 번씩 나오는지 출력

1
2
3
4
5
6
7
from collections import Counter

df = x.copy()
for fruit in df['fruit'].unique(): 
  all_returns = df_반품[df_반품['fruit'] == fruit]['location'].dropna().tolist()
  all_counter = Counter(all_returns)
  print(all_counter)
1
2
3
4
5
6
7
Counter({'4호점': 2})
Counter({'5호점': 4, '4호점': 2})
Counter({'5호점': 1, '1호점': 1, '2호점': 1, '4호점': 1})
Counter({'5호점': 3})
Counter({'4호점': 1, '2호점': 1, '1호점': 1, '5호점': 1})
Counter({'1호점': 1, '3호점': 1})
Counter({'4호점': 2, '2호점': 1})




입고 시트에서 type이 반품인 경우 returns 값이 비어있을 때

반품 시트의 과일명과 일치하는 location을 찾아 returns의 데이터에 값을 넣는 과정을 진행했다.

여기서 문제는 같은 과일이더라도 location이 다르다 라는 것이다.


어떤 식으로 값을 넣을까 고민하다가 Counter를 통해서

반품 시트에서 과일별 location 개수를 Counter로 저장하고

입고 시트에서 returns 값이 이미 있는 경우 해당 location의 개수를 -1한다.

Counter에서 value가 0이상인 값들만 결측치에 하나씩 넣는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
for fruit in df_입고[df_입고['type'] == '반품']['fruit'].unique():

    refund_locs = df_반품[df_반품['fruit'] == fruit]['location'].tolist()
    refund_counter = Counter(refund_locs) # {location : count}

    existing_returns = df_입고[
        (df_입고['fruit'] == fruit) & (df_입고['type'] == '반품') 
        & (df_입고['returns'].notna())]['returns'].tolist()

    for loc in existing_returns: # notna_returns list 
        refund_counter[loc] -= 1

    fill_loc = []
    for loc, count in refund_counter.items(): # 데이터의 key, value 값 튜플
        if count > 0:
            fill_loc.extend([loc] * count)

    nan_index = df_입고[
        (df_입고['fruit'] == fruit) & (df_입고['type'] == '반품') 
        & (df_입고['returns'].isna())].index.tolist()

    for idx, loc in zip(nan_index, fill_loc):
        df_입고.at[idx, 'returns'] = loc



1
2
3
4
5
6
expected = df_반품.groupby(['fruit', 'location']).size().unstack(fill_value=0)
actual = df_입고[df_입고['type'] == '반품'].groupby(['fruit', 'returns']).size().unstack(fill_value=0)

comparison = expected.subtract(actual, fill_value=0)
comparison['diff'] = comparison.abs().sum(axis=1)
comparison
fruit1호점2호점3호점4호점5호점diff
Apple000000
Apricot000000
Banana000000
Blueberry000000
Cherry000000
Durian000000
Grape000000
Grapefruit000000
Guava000000
Kiwi000000
Lychee000000
Mango000000
Melon 000000
Orange000000
Peach 000000
Pear000000
Strawberry000000
Watermelon 000000

모두 알맞게 들어갔다.



반품이 아닌 경우 returns ‘-‘로 표시하기

1
df_입고
.idxfruitdatereturnstype
002_ApApple2507021호점반품
103_StStrawberry2507032호점입고
205_MaMango2507054호점입고
306_GrGrape2507063호점반품
408_KiKiwi2507084호점반품
9905_MaMango2507053호점입고
10006_GrGrape2507061호점입고
10107_OrOrange2507075호점-
10212_WaWatermelon2507125호점반품
10321_MaMangosteen2507214호점입고

104 rows × 5 columns



1
df_입고['type'].value_counts()
typecount
입고54
반품48
’-‘2

총 56개를 수정해야한다.



1
df_입고['returns'].value_counts()
returnscount
5호점27
4호점22
3호점21
1호점19
2호점15

returns는 1~5호점까지 존재한다.



1
df_입고.loc[df_입고['type'] != '반품', 'returns'] = '-'
1
df_입고['returns'].value_counts()
returnscount
’-‘56
5호점16
4호점13
1호점8
2호점6
3호점5

56개의 데이터가 ‘-‘ 로 표시된 것을 확인할 수 있다.



type이 ‘-‘인 경우 idx도 ‘-‘ 표시

1
df_입고['type'].value_counts()
typecount
입고54
반품48
’-‘2

’-‘로 되어있는 데이터가 2개 있다.



1
df_입고['idx'].unique()
1
2
3
4
array(['02_Ap', '03_St', '05_Ma', '06_Gr', '08_Ki', '10_Me', '11_Pe',
       '25_Gu', '15_Ba', '20_Du', '22_Ap', '23_Ly', '18_Ch', '04_Le',
       '07_Or', '12_Wa', '09_Pi', '21_Ma', '14_Pe', '16_Bl', '17_Co',
       '19_Gr'], dtype=object)



1
df_입고.loc[df_입고['type']=='-', 'idx'] = '-'
1
df_입고['idx'].unique()
1
2
3
4
array(['02_Ap', '03_St', '05_Ma', '06_Gr', '08_Ki', '10_Me', '11_Pe',
       '25_Gu', '15_Ba', '20_Du', '22_Ap', '23_Ly', '18_Ch', '04_Le', '-',
       '07_Or', '12_Wa', '09_Pi', '21_Ma', '14_Pe', '16_Bl', '17_Co',
       '19_Gr'], dtype=object)

’-‘ 가 추가되었다.





참고) Counter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import Counter

c = Counter(['A', 'B', 'A', 'C'])
print(c)  # Counter({'A': 2, 'B': 1, 'C': 1})


print(c['A'])  # 2


print(c['D'])  # 0


c['A'] -= 1
print(c)  # Counter({'A': 1, 'B': 1, 'C': 1})






REFERENCE

Counter

This post is licensed under CC BY 4.0 by the author.