累积分布图Python

39

我正在使用Python做一个项目,有两个数据数组。我们称它们为pcpnc。我需要在同一张图上绘制两者的累积分布。对于pc,它应该是一个小于图,即在(x,y)处,pc中的y点必须小于x的值。对于pnc,它应该是一个大于图,即在(x,y)处,pnc中的y点必须大于x的值。

我尝试使用直方图函数- pyplot.hist。有没有更好、更容易实现我想要的功能的方法呢?另外,它必须以对数刻度在x轴上绘制。


2
如果您展示一下您迄今为止的尝试——样本输入数据、期望输出等等,那将会很有帮助。否则这个问题看起来就像是一个“给我看代码”的问题。 - Jon Clements
2
延伸Jon的评论,人们更愿意帮助您修复现有的代码,而不是从头开始生成代码。无论您的代码有多么有缺陷和不可用,都要展示出来并解释一下:a)您期望它做什么;b)它目前正在做什么。 - tacaswell
5个回答

49

你已经接近了。不应该使用plt.hist作为numpy.histogram,因为它会同时给出值和区间,然后你可以轻松地绘制累积图:

import numpy as np
import matplotlib.pyplot as plt

# some fake data
data = np.random.randn(1000)
# evaluate the histogram
values, base = np.histogram(data, bins=40)
#evaluate the cumulative
cumulative = np.cumsum(values)
# plot the cumulative function
plt.plot(base[:-1], cumulative, c='blue')
#plot the survival function
plt.plot(base[:-1], len(data)-cumulative, c='green')

plt.show()

在此输入图像描述


你忘记在cumsum之前加上np了,因为你的np.histogram命令暗示了需要它。 - ehsteve
@ehsteve已修正答案。 - Gabriel
1
使用直方图既不必要又不精确。 - Eric O. Lebigot
@EOL 在大数组中是必要的,否则你会耗尽内存。 - aaren
确实,但我认为这不是问题的特定情况,更多的是关于如何获得累积分布而不是在大数组的情况下进行近似处理。 - Eric O. Lebigot
这并不完全是一个CDF: https://en.wikipedia.org/wiki/Cumulative_distribution_function。 - Alex

33

使用直方图确实不必要且不够精确(分箱使数据模糊):您可以只对所有x值进行排序:每个值的索引是较小值的数量。这种更简短和简单的解决方案如下:

import numpy as np
import matplotlib.pyplot as plt

# Some fake data:
data = np.random.randn(1000)

sorted_data = np.sort(data)  # Or data.sort(), if data can be modified

# Cumulative counts:
plt.step(sorted_data, np.arange(sorted_data.size))  # From 0 to the number of data points-1
plt.step(sorted_data[::-1], np.arange(sorted_data.size))  # From the number of data points-1 to 0

plt.show()
此外,更适当的绘图样式确实是plt.step()而不是plt.plot(),因为数据在离散位置上。
结果是:

enter image description here

你可以看到它比EnricoGiampieri答案的输出更加崎岖,但这是真实的直方图(而不是近似的、模糊的版本)。 PS:正如SebastianRaschka所指出的,最后一个点应该理想地显示总数(而不是总数-1)。这可以通过以下方式实现:
plt.step(np.concatenate([sorted_data, sorted_data[[-1]]]),
         np.arange(sorted_data.size+1))
plt.step(np.concatenate([sorted_data[::-1], sorted_data[[0]]]),
         np.arange(sorted_data.size+1))

data中有很多点,如果没有缩放效果是看不到的,但当数据只包含少量点时,最后一个点在总数上确实很重要。


2
然而,对于大数组,您希望采用直方图方法,因为它不需要太多的内存。使用“plt.step”方法处理6000万元素的数组时会出现内存错误。 - aaren
同意。我不确定问题是出在plt.step还是这种方法使用了可能是数组的3倍的内存,或者两者都有... - Eric O. Lebigot
我同意:plt.step 可能是绘制“计数”更合适的方法。一个问题:您不需要使用 plt.step(sorted_data, np.arange(1, data.size+1)) 来获取正确的计数吗? - user2489252
1
@SebastianRaschka:说得好。你是正确的。一个完美的解决方案应该加上这个最后一点。这可以通过复制最后一个横坐标并在最后一个纵坐标上添加总计数(5)来实现。我更新了答案,谢谢! - Eric O. Lebigot
1
谢谢你的更新。你的解决方案看起来比我的好多了 :) - user2489252
显示剩余5条评论

15

在与@EOL的充分讨论后,我想发布我的解决方案(左上角)使用随机高斯样本作为摘要:

enter image description here

import numpy as np
import matplotlib.pyplot as plt
from math import ceil, floor, sqrt

def pdf(x, mu=0, sigma=1):
    """
    Calculates the normal distribution's probability density 
    function (PDF).  

    """
    term1 = 1.0 / ( sqrt(2*np.pi) * sigma )
    term2 = np.exp( -0.5 * ( (x-mu)/sigma )**2 )
    return term1 * term2


# Drawing sample date poi
##################################################

# Random Gaussian data (mean=0, stdev=5)
data1 = np.random.normal(loc=0, scale=5.0, size=30)
data2 = np.random.normal(loc=2, scale=7.0, size=30)
data1.sort(), data2.sort()

min_val = floor(min(data1+data2))
max_val = ceil(max(data1+data2))

##################################################




fig = plt.gcf()
fig.set_size_inches(12,11)

# Cumulative distributions, stepwise:
plt.subplot(2,2,1)
plt.step(np.concatenate([data1, data1[[-1]]]), np.arange(data1.size+1), label='$\mu=0, \sigma=5$')
plt.step(np.concatenate([data2, data2[[-1]]]), np.arange(data2.size+1), label='$\mu=2, \sigma=7$') 

plt.title('30 samples from a random Gaussian distribution (cumulative)')
plt.ylabel('Count')
plt.xlabel('X-value')
plt.legend(loc='upper left')
plt.xlim([min_val, max_val])
plt.ylim([0, data1.size+1])
plt.grid()

# Cumulative distributions, smooth:
plt.subplot(2,2,2)

plt.plot(np.concatenate([data1, data1[[-1]]]), np.arange(data1.size+1), label='$\mu=0, \sigma=5$')
plt.plot(np.concatenate([data2, data2[[-1]]]), np.arange(data2.size+1), label='$\mu=2, \sigma=7$') 

plt.title('30 samples from a random Gaussian (cumulative)')
plt.ylabel('Count')
plt.xlabel('X-value')
plt.legend(loc='upper left')
plt.xlim([min_val, max_val])
plt.ylim([0, data1.size+1])
plt.grid()


# Probability densities of the sample points function
plt.subplot(2,2,3)

pdf1 = pdf(data1, mu=0, sigma=5)
pdf2 = pdf(data2, mu=2, sigma=7)
plt.plot(data1, pdf1, label='$\mu=0, \sigma=5$')
plt.plot(data2, pdf2, label='$\mu=2, \sigma=7$')

plt.title('30 samples from a random Gaussian')
plt.legend(loc='upper left')
plt.xlabel('X-value')
plt.ylabel('probability density')
plt.xlim([min_val, max_val])
plt.grid()


# Probability density function
plt.subplot(2,2,4)

x = np.arange(min_val, max_val, 0.05)

pdf1 = pdf(x, mu=0, sigma=5)
pdf2 = pdf(x, mu=2, sigma=7)
plt.plot(x, pdf1, label='$\mu=0, \sigma=5$')
plt.plot(x, pdf2, label='$\mu=2, \sigma=7$')

plt.title('PDFs of Gaussian distributions')
plt.legend(loc='upper left')
plt.xlabel('X-value')
plt.ylabel('probability density')
plt.xlim([min_val, max_val])
plt.grid()

plt.show()

12
为了为社区做出自己的贡献,我在这里分享我的绘制直方图的函数。这是我理解问题的方式,即同时绘制直方图和累积直方图。
def hist(data, bins, title, labels, range = None):
  fig = plt.figure(figsize=(15, 8))
  ax = plt.axes()
  plt.ylabel("Proportion")
  values, base, _ = plt.hist( data  , bins = bins, normed=True, alpha = 0.5, color = "green", range = range, label = "Histogram")
  ax_bis = ax.twinx()
  values = np.append(values,0)
  ax_bis.plot( base, np.cumsum(values)/ np.cumsum(values)[-1], color='darkorange', marker='o', linestyle='-', markersize = 1, label = "Cumulative Histogram" )
  plt.xlabel(labels)
  plt.ylabel("Proportion")
  plt.title(title)
  ax_bis.legend();
  ax.legend();
  plt.show()
  return

如果有人想知道它是什么样子,请查看(使用seaborn激活):

enter image description here

此外,关于双网格线(白色线条),我过去总是很难得到漂亮的双网格线。以下是一个有趣的方法来解决这个问题:如何将次坐标轴的网格线放在主绘图区域后面?


1
如果您的数组中可能会出现负值,那么您可能希望取绝对值...否则累积直方图将会出现偏差。 - dv3

6
生成此图最简单的方法是使用 seaborn
import seaborn as sns  
sns.ecdfplot()

这里是文档

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接