Saturday, November 21, 2015

Adding a Sales Profile to a Discrete Event Simulation

If you've been following along, you'll know I've been putting together a discrete event simulator for inventory management.  The goal is to better integrate knowledge of sales history, stock levels, and ordering patterns to make quantitative decisions about what to order and when.  So far I've been using a scenario involving a 24 hour business with a constant sales rate, but that's not realistic.  Therefore the next step is to add a way to generate a customer flow based on a sales profile.

The way I've gone about doing this is to start with a graph of a sales profile.  Let's slightly change the scenario I've been using.  We're now looking at a profile of sales at a fruit shop.  The fruit shop only opens between 8am and 9pm on weekdays, 8am to 5pm on Saturdays, and 9am to 6pm on Sunday. Weekday sales are $3000 a day, and weekend sales are $7500 a day.  This is a total of $30000 per week.  When graphed against seconds passed, the total sales for the week are shown below.
sales graph
sales profile
In the previous simulations I was modelling the customer flow as a poisson process, where item sales per second was used to generate random times between customer arrivals.  This makes it hard to create a varying sales pattern based on customer flow.  This is easily changed though, you just need to interpret the problem differently.  If for example 150 watermelons are sold per week, that means that on average $200 are spent in the store as a whole between each watermelon purchase.  If we assume that the customer flow of people buying watermelons is similar to those buying fruit in general, which in the absence of better data is a reasonable assumption, a sales pattern can now be generated.

If instead of saying "a customer will buy a watermelon at x seconds into the week" we now say "a customer will buy a watermelon at x dollars into the week", it makes sense to reverse the situation and use the sales as our independent variable.  The way to do that is indicated in the image above.  The evenly spaced horizontal black lines indicate sales increments of $1000.  When projected across to the sales profile and then down to the time axis, a customer flow is generated.  You can see that there won't be any sales when the store is closed as the line can't intersect with these time periods.  Days like Saturday and Sunday that have a high rate of sales intersect a lot of lines and project them into a small time period.

I've written a custom class to perform this interpolation process on a piecewise function that's passed to it.  It allows a dollar value to be converted to a time stamp and vice versa.  The supplied interp function in numpy is almost adequate, but it has some extra constraints placed on it.  Firstly the data supplied needs to have unique timestamps in increasing order, it doesn't make sense to have two different sales values for a single point in time.  The other requirement is that the sales data needs to be increasing, in this case it's fine to have two data points the same, it just means no sales have occurred in this period.

To ensure consistency, I wanted to ensure that when converting from dollars to time, predictable and consistent results were produced.  This may not seem obvious, but if you were to convert the dollar value of 3000 to a timestamp in the above example there are multiple answers.  Any time between closing on the first day and opening on the second day the sales value is $3000.  I decided to make it so the earliest time a particular dollar value is reached would be returned.

It's simply a matter of creating a piecewise function as shown below.  It can be as detailed as you want.  I've only indicated sales at opening and closing times and linearly interpolated between the data points, but you could supply hourly or minute by minute sales data if you wanted.

python code
specification of a sales profile

Using this function, a customer pattern is generated in terms of dollars and then back projected to timestamps.  These values are then used in the simulation.  I haven't changed the ordering requirements from the previous scenario.  You have to order 24 items at a time, the deliveries happen at 1 am, and after the delivery driver has left, you have to have 50 or more items.  So what does the simulation look like now?
simulation results
stock on hand simulation with sales profile
You can see the plateaus where there are no sales because the store is closed.  The deliveries at 1 am also appear as interruptions to the plateaus.  Also visible are the slow weekdays sales of $3000 over 13 hours.  This can be compared to the weekend sales of $7500 over 9 hours.  This increased rate of sales of approximately 3.6x on a weekend is visible as well.

I'm not happy with my code at the moment so I'll take some time to refactor it and change some of the terminology to generalise some parts.  My main concern at the moment is to create good libraries, the test.py program is sort of a test bed for these.
https://github.com/GrantTrebbin/DiscreteEventSimulator/commit/eb24b7c44298c428170db3490f4d57e4062de7af
Get the code!

No comments:

Post a Comment