百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

独家 | 使用python马尔科夫链方法建模星巴克等待时长

off999 2024-11-24 20:11 16 浏览 0 评论

作者:Piero Paialunga

翻译:陈超

校对:和中华

本文约4200字,建议阅读11分钟

本文使用马尔科夫链的方法对星巴克购买咖啡的等待时长进行建模。


以下内容关于如何使用马尔科夫链计算你在星巴克咖啡的等待时长。


图片来自Unplash, Jon Tyson摄


我来自意大利,可以肯定的说,咖啡就是信仰。我们喝咖啡来社交,在早上喝来唤醒我们,在午餐、晚餐后也会喝咖啡。如果好久未见某个朋友,我们会说


“Vieni a prenderti un caffè”


意思是

“来喝杯咖啡吧”


我住在美国,美国人喝咖啡的方式完全不同。我上班的时候会选择将咖啡带走喝。在工作的时候喝咖啡。看电影的时候喝咖啡。美国人不喝“意式浓缩”,但是他们很享受花很长的时间喝一大整杯。此外:他们有很多种咖啡!


走进星巴克你可能会看到上百种咖啡。有黑咖啡,黑塔玛奇朵,拿铁或者星冰乐,或者是其他很多我叫不上名字的品类。


这里有很多容易制作的,也有很多复杂的咖啡。假设你在星巴克排队点咖啡,如果前面有三个人都点了黑咖啡,那你可能需要3分钟拿到你的咖啡。


然而,如果他们点了“额外鲜奶油加糖屑肉桂豆浆的焦糖玛奇朵”……那你的等待时长可能会加倍,或者至少你得多等几分钟。


所以问题是……


“我要等多久才能拿到咖啡,在此期间我可以写篇关于我要等多久才能拿到咖啡的文章吗?”


当然,我们无法获知其他人要点什么,所以这是一个概率问题(或者如果你想要一个随机过程)。所以我们该如何解决它呢?


一个可行的方法是建立马尔科夫链。或者说,我们需要一个时间依赖的马尔科夫链


让我们来解决问题吧!


1. 理论介绍


让我们从问题的理论背景介绍和纠正错误开始。


先从最简单的例子开始。我们进入星巴克开始点单。用数学语言来说,我们可能有三种状态。


第一种状态(O)是我们点咖啡。第二种状态(M)我们已经点完了咖啡并在等待。M状态导致他们帮你做咖啡。然后你拿到咖啡并转移到离开状态(L)。这意味着你的咖啡已经做好了你也可以走了。



图片来自作者


好的。现在,可能的转移状态有哪些?

1) 你从点单到制作(O到M)

2) 也能从M到M(持续等待)

3) 最后你从M到L



图片来自作者


我们如何将其形式化呢?


1.1 关于马尔科夫链

马尔科夫链的假设是什么?具体如下:


“处入下一状态的概率仅依赖于当前状态”


例如:


在t=5的时候处入离开状态L的概率仅仅依赖于你在t=4时处于制作状态M中。


让我们将其形式化:



图片来自作者


在上述符号系统当中,我们在时间t时状态的概率,在空间s_t (O, M, L) 里仅依赖于我们在t-1时的状态。


在实验中我们还需要记住的是,t时刻的概率也是时间依赖的。因为,如果你已经等了5分钟,在下一分钟离开的概率就比只等待1分钟后离开的概率更大。


这意味着:



图片来自作者


这就是文章开头提到过的概念。


当然,星巴克不仅有我们在,也有很多其他顾客在!所以我们马上会把这个设置复杂化。但上述是一切的起点。


接下来我们正式开始!


2. 一顾客一饮品的例子


让我们从最简单的情况开始。我们知道要喝什么并且我们是咖啡店唯一的顾客。


假设我们想要一杯焦糖玛奇朵。allrecipes食谱网站说做一杯需要5分钟。假设我们需要30秒来点单和支付。所以总共等待时间可能有5分30秒。但是让我们更进一步,5分钟只是平均制作时间,一般要用4-6分钟:



图片来自作者


好的。我们假设时间刻度是30秒(0.5分钟)。我们在30秒后有可能拿到我们的咖啡,也有可能8分钟之后我们仍然在等待。


2.1 动手实现


让我们来执行一下这个过程。首先导入库:


import numpy as np 
import matplotlib.pyplot as plt 
import pandas as pd 
from scipy import signal
import sklearn 
import seaborn as sns
Hosted on Jovian


接下来定义状态:


customer_state = ['Ordering the coffee','Waiting for your coffee','Leaving']
states = {'O':customer_state[0],'M':customer_state[1],'L':customer_state[2]}


接下来我们使用下面这个函数运行上述过程:


mu, sigma = 5,1
def one_drink_one_cust():
    start = states['O']
    print(start+'\n')
    ordering_time = 0.5
    first_state = states['M']
    print(first_state+'\n')
    waiting_time = 0
    k = 0
    while k == 0:
        p = stats.norm.cdf(waiting_time, loc=mu, scale=sigma)
        k = np.random.choice([0,1],p = [1-p,p])
        waiting_time = waiting_time+0.5
        if k == 0:
            print('Coffee is brewing... \n')
    print('Your coffee is ready! \n')
    print(states['L']+'\n')
    print('Waiting time is = %.2f'%(waiting_time))
    return waiting_time


这里是一次运行例子:


运行 one_drink_one_cust()




3. 一顾客多饮品


现在,让我们增加模型复杂性让情况更真实一些。


我们并不知道特定的顾客想要啥。而这种情况更真实的原因在于星巴克有多达150种饮品,且他们可能需要不同的等待时长。


现在马尔科夫链看起来像这样



图片来自作者


与之前的一个例子不同之处在于,这里有一个概率分布,我们叫做:



图片来自作者


特别地,所有可能的饮品有一个概率分布,我们叫做:



图片来自作者


例如:



图片来自作者


所以我们可以说:


图片来自作者




3.1实际操作

我们可以从kaggle数据集

(https://www.kaggle.com/datasets/ashishpatel26/starbucks-menu-nutrition-drinks)导入所有的


星巴克饮品:


pd.read_csv('starbucks-menu-nutrition-drinks.csv')





177 rows × 7 columns
Hosted on Jovian


我们不需要其他列。


我们不知道最热门的单品是哪一个,所以我们做一个假的分布:


kind_of_coffee = np.array(pd.read_csv('starbucks-menu-nutrition-drinks.csv')['Unnamed: 0'])
p_start = []
for i in range(len(kind_of_coffee)):
    p_start.append(np.random.choice(np.arange(50,100)))
p_start = np.array(np.array(list(np.array(p_start)/sum(p_start))))
Hosted on Jovian 


我们从50-100提取值,然后我们用和进行归一化,得到一个概率向量


coffe_picked = []
for i in range(10000):
    coffe_picked.append(np.random.choice(range(0,len(kind_of_coffee)),p=p_start))
sns.displot(coffe_picked)




我们有了自己的概率分布。我们接下来要做什么?概率分布将会为我们作出选择:我们将从分布中取样。然后假设每种咖啡需要不同的制作时间。


现在,客观来说,没有太大的差异。假设需要3分钟(平均时长)来完成一杯最“简单”的咖啡(可能是黑咖啡),6分钟(平均时长)来完成一杯最复杂的咖啡(额外鲜奶油加糖屑肉桂豆浆的焦糖玛奇朵)


我们把方差变化范围取到0.1-1。


coffee_data = pd.DataFrame(kind_of_coffee,columns=['State 1'])
mu_list = []
var_list = []
for i in range(len(coffee_data)):
    mu_list.append(np.random.choice(np.linspace(3,6,1000)))
    var_list.append(np.random.choice(np.linspace(0.1,1.5,1000)))
coffee_data[r'$\mu#39;]=mu_list
coffee_data[r'$\sigma#39;]=var_list
coffee_data[r'$p#39;] = p_start
coffee_data.head()




我们的均值可以从3-6取值,平均等待时长也可以从3-6分钟,有时不确定性更大。例如,有可能当你喝咖啡A的时候,等待的时间更长。


用代码来实现这一情形:


def random_drink_one_cust():
    start = states['O']
    print(start+'\n')
    ordering_time = 0.5
    first_state = states['M']
    chosen_i = np.random.choice(range(0,len(kind_of_coffee)),p=p_start)
    print('Ordering coffee %s'%(kind_of_coffee[chosen_i]))
    print(first_state+'\n')




    mu_i, var_i = coffee_data[r'$\mu#39;].loc[chosen_i], coffee_data[r'$\sigma#39;].loc[chosen_i]
    waiting_time = 0
    k = 0
    while k == 0:
        p = stats.norm.cdf(waiting_time, loc=mu_i, scale=var_i)
        k = np.random.choice([0,1],p = [1-p,p])
        waiting_time = waiting_time+0.5
        if k == 0:
            print('Coffee is brewing... \n')
    print('Your coffee is ready! \n')
    print(states['L']+'\n')
    print('Waiting time is = %.2f'%(waiting_time))
    return waiting_time
random_drink_one_cust()


输出:


Ordering the coffee


Ordering coffee Teavana? Shaken Iced Black Tea

Waiting for your coffee


Coffee is brewing...


Coffee is brewing...


Coffee is brewing...


Coffee is brewing...


Coffee is brewing...


Coffee is brewing...


Coffee is brewing...


Your coffee is ready!


Leaving


Waiting time is = 4.00

4.0


现在,我相信我们可以提取一些额外的有价值的信息。例如……


“不管你在星巴克点什么咖啡,你通常需要等多久呢?”


这一个问题非常宽泛,所以我们也可以用一些统计数据来回答:


waiting_time_list = []
for i in range(10000):
    waiting_time_list.append(random_drink_one_cust())
plt.figure(figsize=(20,10))
plt.subplot(2,1,1)
sns.histplot(waiting_time_list,fill=True)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.xlabel('Waiting time (minutes)',fontsize=14)
plt.ylabel('Count',fontsize=14)
plt.subplot(2,1,2)
sns.kdeplot(waiting_time_list,fill=True)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.xlabel('Waiting time (minutes)',fontsize=14)
plt.ylabel('Probability Density Function (PDF)',fontsize=14)




Text(0, 0.5, 'Probability Density Function (PDF)')




我改变了函数,去掉了所有的“print”,以便于我的电脑不会爆炸。


4. 多顾客多饮品


好的。现在我们有多名顾客,我们怎么办呢?现在的情形看起来像这样:



图片来自作者


现在去思考每次只能等前一杯咖啡做完才能做下一杯的情况已经不现实了。星巴克通常都有3,4个咖啡师。


这会让代码变得有点复杂,因为我们需要追踪所有咖啡师是否忙碌的事实。由于这个原因,该函数我们将设计为顾客数和咖啡师数量的函数。


函数如下:


def random_drink_multiple_cust(cust=2,num_baristas =5):
    time_of_process = []
    baristas = np.zeros(num_baristas)
    q = 0
    ordering_time = 0
    for c in range(cust):
        start = states['O']
        print('Customer number %i is ordering'%(c))
        ordering_time+=0.5
        if sum(baristas)!=num_baristas:
            print('There is at least one free baristas! :)')
            waiting_time = random_drink_one_cust()
            time_of_process.append(waiting_time+ordering_time)
            baristas[q] = 1
            q = q + 1 
        if len(time_of_process)==cust:
            break
        if sum(baristas)==num_baristas:
            print('All the baristas are busy :(')
            print('You have to wait an additional %i minutes until they can start making your coffee' %(min(time_of_process)))
            waiting_time = min(time_of_process)+random_drink_one_cust()+ordering_time
            baristas[num_baristas-1]=0
            time_of_process.append(waiting_time)
            q = q-1
        if len(time_of_process)==cust:
            break
    print('The waiting time for each customer is:')
    print(time_of_process)
    return time_of_process


好的,让我们来测试一下4个顾客和2个咖啡师的情况。


random_drink_multiple_cust(4,2)
Customer number 0 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Iced Caffè Americano
Waiting for your coffee




Your coffee is ready! 




Leaving




Customer number 1 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Iced Skinny Mocha
Waiting for your coffee




Your coffee is ready! 




Leaving




All the baristas are busy :(
You have to wait an additional 6 minutes until they can start making your coffee
Ordering the coffee




Ordering coffee Tazo? Bottled Organic Iced Black Tea
Waiting for your coffee




Your coffee is ready! 




Leaving




Customer number 2 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Starbucks? Bottled Caramel Frappuccino? Coffee Drink
Waiting for your coffee




Your coffee is ready! 




Leaving




The waiting time for each customer is:
[7.0, 6.5, 14.0, 5.5]
[7.0, 6.5, 14.0, 5.5]


在前两个例子中,咖啡师是有空的情况下,他们分别要等待7分钟和6.5分钟。


注意?这不是错误!这只意味着第二杯咖啡快于第一杯咖啡+点单的时间和。你仔细想想就可以想得通。如果你点了一杯复杂的咖啡,我只点了一杯拿铁,我可以比你等的时间更短,即使你在我之前点单。


然后是咖啡师忙的情况。我们不得不在点单之前额外等6分钟。(我可以让情况更真实一点,并减去1分钟点单时间,但是你的想法是对的吗?)


到第四个顾客点单时,有一个空闲的咖啡师,所以他不用再等那么长时间,只需要等5.5分钟。


注意到这个模型的复杂度以及情况的有趣性。以下是10个顾客和5个咖啡师:


random_drink_multiple_cust(10,5)
Customer number 0 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Iced Coconutmilk Mocha Macchiato
Waiting for your coffee




Your coffee is ready! 




Leaving




Customer number 1 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Coffee Frappuccino? Blended Coffee
Waiting for your coffee




Your coffee is ready! 




Leaving




Customer number 2 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Strawberry Acai Starbucks Refreshers? Beverage
Waiting for your coffee




Your coffee is ready! 




Leaving




Customer number 3 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Shaken Sweet Tea
Waiting for your coffee




Your coffee is ready! 




Leaving




Customer number 4 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Coffee Traveler
Waiting for your coffee




Your coffee is ready! 




Leaving




All the baristas are busy :(
You have to wait an additional 5 minutes until they can start making your coffee
Ordering the coffee




Ordering coffee Starbucks? Doubleshot Protein Vanilla
Waiting for your coffee




Your coffee is ready! 




Leaving




Customer number 5 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Coconutmilk Mocha Macchiato
Waiting for your coffee




Your coffee is ready! 




Leaving




All the baristas are busy :(
You have to wait an additional 5 minutes until they can start making your coffee
Ordering the coffee




Ordering coffee Nitro Cold Brew with Sweet Cream
Waiting for your coffee




Your coffee is ready! 




Leaving




Customer number 6 is ordering
There is at least one free baristas! :)
Ordering the coffee




Ordering coffee Starbucks? Low Calorie Iced Coffee + Milk
Waiting for your coffee




Your coffee is ready! 




Leaving




All the baristas are busy :(
You have to wait an additional 5 minutes until they can start making your coffee
Ordering the coffee




Ordering coffee Tazo? Bottled Lemon Ginger
Waiting for your coffee




Your coffee is ready! 




Leaving




The waiting time for each customer is:
[5.5, 5.5, 7.0, 8.0, 6.5, 12.5, 8.5, 12.5, 7.5, 16.0]
[5.5, 5.5, 7.0, 8.0, 6.5, 12.5, 8.5, 12.5, 7.5, 16.0]


5. 注意事项


在本文中,我们讨论一个排队等待时长的一般性问题。有不同的场景:


1) 我们可能是商店唯一顾客且明确知道自己想要买什么

2) 我们可能是商店唯一顾客但不知道自己想要买什么

3) 我们不是商店唯一顾客且不知道自己想要买什么


这三个场景依次变得复杂。由此,马尔科夫链也会变得越来越复杂。


1) 在第一个例子里,唯一的概率分布是煮咖啡的过程。例如,需要花费平均时长5分钟来做焦糖玛奇朵,制作时长是方差为1的高斯分布

2) 在第二个例子里,我们得到了所选咖啡的概率分布加上制作咖啡的概率分布,也就是之前那个

3) 第三个例子里,我们用上述两种情况,并且咖啡师变成了另外一个需要考虑的因素


我们可以让那个这个模型更复杂一点。我们可以考虑这样一个事实:机器是可以坏的。我们也可以考虑咖啡师人手不够的情况。可以考虑咖啡师人数随时间变化,也可以考虑一些咖啡师比另外一些人操作更快。以及更多可能性。


这个起点可以用来以几乎所有你想要的方式使模型复杂化。只要记住,我们使用的是时间相关马尔可夫链工具,并以此为基础进行构建。


原文标题:

Modeling Starbucks Waiting Time Using Markov Chains, with Python

原文链接:

https://towardsdatascience.com/modeling-starbucks-waiting-time-using-markov-chains-with-python-ab267670d02c

相关推荐

Python四种常用的高阶函数,你会用了吗

每天进步一点点,关注我们哦,每天分享测试技术文章本文章出自【码同学软件测试】码同学公众号:自动化软件测试码同学抖音号:小码哥聊软件测试1、什么是高阶函数把函数作为参数传入,这样的函数称为高阶函数例如:...

Python之函数进阶-函数加强(上)(python函数的作用增强代码的可读性)

一.递归函数递归是一种编程技术,其中函数调用自身以解决问题。递归函数需要有一个或多个终止条件,以防止无限递归。递归可以用于解决许多问题,例如排序、搜索、解析语法等。递归的优点是代码简洁、易于理解,并...

数据分析-一元线性回归分析Python

前面几篇介绍了数据的相关性分析,通过相关性分析可以看出变量之间的相关性程度。如果我们已经发现变量之间存在明显的相关性了,接下来就可以通过回归分析,计算出具体的相关值,然后可以用于对其他数据的预测。本篇...

python基础函数(python函数总结)

Python函数是代码复用的核心工具,掌握基础函数的使用是编程的关键。以下是Python函数的系统总结,包含内置函数和自定义函数的详细用法,以及实际应用场景。一、Python内置函数(...

python进阶100集(9)int数据类型深入分析

一、基本概念int数据类型基本上来说这里指的都是整形,下一届我们会讲解整形和浮点型的转化,以及精度问题!a=100b=a这里a是变量名,100就是int数据对象,b指向的是a指向的对象,...

Python学不会来打我(73)python常用的高阶函数汇总

python最常用的高阶函数有counter(),sorted(),map(),reduce(),filter()。很多高阶函数都是将一个基础函数作为第一个参数,将另外一个容器集合作为第二个参数,然...

python中有哪些内置函数可用于编写数值表达式?

在Python中,用于编写数值表达式的内置函数很多,它们可以帮助你处理数学运算、类型转换、数值判断等。以下是常用的内置函数(不需要导入模块)按类别归类说明:一、基础数值处理函数函数作用示例ab...

如何在Python中获取数字的绝对值?

Python有两种获取数字绝对值的方法:内置abs()函数返回绝对值。math.fabs()函数还返回浮点绝对值。abs()函数获取绝对值内置abs()函数返回绝对值,要使用该函数,只需直接调用:a...

【Python大语言模型系列】使用dify云版本开发一个智能客服机器人

这是我的第359篇原创文章。一、引言上篇文章我们介绍了如何使用dify云版本开发一个简单的工作流:【Python大语言模型系列】一文教你使用dify云版本开发一个AI工作流(完整教程)这篇文章我们将引...

Python3.11版本使用thriftpy2的问题

Python3.11于2022年10月24日发布,但目前thriftpy2在Python3.11版本下无法安装,如果有使用thriftpy2的童鞋,建议晚点再升级到最新版本。...

uwsgi的python2+3多版本共存(python多版本兼容)

一、第一种方式(virtualenv)1、首先,机器需要有python2和python3的可执行环境。确保pip和pip3命令可用。原理就是在哪个环境下安装uwsgi。uwsgi启动的时候,就用的哪个...

解释一下Python脚本中版本号声明的作用

在Python脚本中声明版本号(如__version__变量)是一种常见的元数据管理实践,在IronPython的兼容性验证机制中具有重要作用。以下是版本号声明的核心作用及实现原理:一、版本号...

除了版本号声明,还有哪些元数据可以用于Python脚本的兼容性管理

在Python脚本的兼容性管理中,除了版本号声明外,还有多种元数据可以用于增强脚本与宿主环境的交互和验证。以下是一些关键的元数据类型及其应用场景:一、环境依赖声明1.Python版本要求pyth...

今年回家没票了?不,我有高科技抢票

零基础使用抢票开源软件Py12306一年一度的抢票季就要到了,今天给大家科普一下一款软件的使用方法。软件目前是开源的,禁止用于商用。首先需要在电脑上安装python3.7,首先从官网下载对应的安装包,...

生猛!春运抢票神器成GitHub热榜第一,过年回家全靠它了

作者:车栗子发自:凹非寺量子位报道春节抢票正在如火如荼的进行,过年回家那肯定需要抢票,每年的抢票大战,都是一场硬战,没有一个好工具,怎么能上战场死锁呢。今天小编推荐一个Python抢票工具,送到...

取消回复欢迎 发表评论: