如何在两个数据类别之间绘制分隔线?

5
我有一个简单的练习,但我不确定该怎么做。我有以下数据集:
男性100
    Year    Time
0   1896    12.00
1   1900    11.00
2   1904    11.00
3   1906    11.20
4   1908    10.80
5   1912    10.80
6   1920    10.80
7   1924    10.60
8   1928    10.80
9   1932    10.30
10  1936    10.30
11  1948    10.30
12  1952    10.40
13  1956    10.50
14  1960    10.20
15  1964    10.00
16  1968    9.95
17  1972    10.14
18  1976    10.06
19  1980    10.25
20  1984    9.99
21  1988    9.92
22  1992    9.96
23  1996    9.84
24  2000    9.87
25  2004    9.85
26  2008    9.69

第二个是:

女性100

    Year    Time
0   1928    12.20
1   1932    11.90
2   1936    11.50
3   1948    11.90
4   1952    11.50
5   1956    11.50
6   1960    11.00
7   1964    11.40
8   1968    11.00
9   1972    11.07
10  1976    11.08
11  1980    11.06
12  1984    10.97
13  1988    10.54
14  1992    10.82
15  1996    10.94
16  2000    11.12
17  2004    10.93
18  2008    10.78


我有以下代码:
y = -0.014*male100['Year']+38

plt.plot(male100['Year'],y,'r-',color = 'b')
ax = plt.gca() # gca stands for 'get current axis'
ax = male100.plot(x=0,y=1, kind ='scatter', color='g', label="Mens 100m", ax = ax)
female100.plot(x=0,y=1, kind ='scatter', color='r', label="Womens 100m", ax = ax)

这段文字的含义是:产生以下结果:

enter image description here

我需要绘制一条恰好在它们之间的线。所以这条线将使所有绿色点位于其下方,而红点位于其上方。我该如何做到这一点?
我尝试了调整 y 的参数,但没有成功。我还尝试对 male100、female100 和它们合并版本(跨行)进行线性回归拟合,但没有得到任何结果。
任何帮助都将不胜感激!
4个回答

6

一种解决方案是使用 支持向量机(SVM)。您可以找到分隔两个点类别的两个边缘。然后,两个支持向量的平均线就是您的答案。请注意,当这两组点是线性可分时才会发生这种情况。 输入图像描述
您可以使用以下代码查看结果:

数据输入

male = [
(1896  ,  12.00),
(1900  ,  11.00),
(1904  ,  11.00),
(1906  ,  11.20),
(1908  ,  10.80),
(1912  ,  10.80),
(1920  ,  10.80),
(1924  ,  10.60),
(1928  ,  10.80),
(1932  ,  10.30),
(1936  ,  10.30),
(1948  ,  10.30),
(1952  ,  10.40),
(1956  ,  10.50),
(1960  ,  10.20),
(1964  ,  10.00),
(1968  ,  9.95),
(1972  ,  10.14),
(1976  ,  10.06),
(1980  ,  10.25),
(1984  ,  9.99),
(1988  ,  9.92),
(1992  ,  9.96),
(1996  ,  9.84),
(2000  ,  9.87),
(2004  ,  9.85),
(2008  ,  9.69)
        ]
female = [
(1928,    12.20),
(1932,    11.90),
(1936,    11.50),
(1948,    11.90),
(1952,    11.50),
(1956,    11.50),
(1960,    11.00),
(1964,    11.40),
(1968,    11.00),
(1972,    11.07),
(1976,    11.08),
(1980,    11.06),
(1984,    10.97),
(1988,    10.54),
(1992,    10.82),
(1996,    10.94),
(2000,    11.12),
(2004,    10.93),
(2008,    10.78)
]

主要代码

请注意这里的C的值非常重要。如果选择为1,则无法获得所需的结果。

from sklearn import svm
import numpy as np
import matplotlib.pyplot as plt

X = np.array(male + female)
Y = np.array([0] * len(male) + [1] * len(female))

# fit the model
clf = svm.SVC(kernel='linear', C=1000) # C is important here
clf.fit(X, Y)
plt.figure(figsize=(8, 4))
# get the separating hyperplane
w = clf.coef_[0]
a = -w[0] / w[1]
xx = np.linspace(-1000, 10000)
yy = a * xx - (clf.intercept_[0]) / w[1]
plt.figure(1, figsize=(4, 3))
plt.clf()
plt.plot(xx, yy, "k-") #********* This is the separator line ************

plt.scatter(X[:, 0], X[:, 1], c=Y, zorder=10, cmap=plt.cm.Paired,
 edgecolors="k")
plt.xlim((1890, 2010))  
plt.ylim((9, 13)) 
plt.show()

2
一种解决方法是几何方法。您可以找到每个数据类的凸包,然后找到一条通过这两个凸包的直线。要找到这条直线,您可以使用this code找到两个凸包之间的内切线,并将其旋转一点。

enter image description here

您可以使用以下代码:
from scipy.spatial import ConvexHull, convex_hull_plot_2d

male = np.array(male)
female = np.array(female)

hull_male = ConvexHull(male)
hull_female = ConvexHull(female)

plt.plot(male[:,0], male[:,1], 'o')
for simplex in hull_male.simplices:
    plt.plot(male[simplex, 0], male[simplex, 1], 'k-')

# Here, the separator line comes from SMV‌ result. 
# Just to show the a separator as an exmple
# plt.plot(xx, yy, "k-")
    
plt.plot(female[:,0], female[:,1], 'o')
for simplex in hull_female.simplices:
    plt.plot(female[simplex, 0], female[simplex, 1], 'k-')
    
plt.xlim((1890, 2010))  
plt.ylim((9, 13)) 

2

我相信您利用回归线的想法是正确的 - 如果不使用它们,那么该线将仅仅是表面的(如果点在混乱数据的情况下重叠,则无法证明)。 因此,我们可以使用一些具有已知线性关系的随机生成的数据来执行以下操作:

import random
import numpy as np
from matplotlib import pyplot as plt
from sklearn.linear_model import LinearRegression

x_values = np.arange(0, 51, 1)

y_points_1 = [i * 2 + random.randint(5, 30) for i in x_points]
y_points_2 = [i - random.randint(5, 30) for i in x_points]

x_points = x_values.reshape(-1, 1)

def regression(x, y):
    model = LinearRegression().fit(x, y)
    y_pred = model.predict(x)
    
    return y_pred

barrier = [(regression(x=x_points, y=y_points_1)[i] + value) / 2 for i, value in enumerate(regression(x=x_points, y=y_points_2))]

plt.plot(x_points, regression(x=x_points, y=y_points_1))
plt.plot(x_points, regression(x=x_points, y=y_points_2))
plt.plot(x_points, barrier)
plt.scatter(x_values, y_points_1)
plt.scatter(x_values, y_points_2)
plt.grid(True)
plt.show()

给我们以下的图表:

enter image description here

如果数据点有重叠,这种方法也适用,所以如果我们稍微改变随机数据并应用相同的过程:
x_values = np.arange(0, 51, 1)

y_points_1 = [i * 2 + random.randint(-10, 30) for i in x_points]
y_points_2 = [i - random.randint(-10, 30) for i in x_points]

我们得到了如下所示的内容:

enter image description here

需要注意的是,这里使用的列表长度相同,因此在应用回归后,您需要向女性数据添加一些预测点,以利用它们之间的线条。这些点仅沿着回归线,并且x值对应于男性数据中存在的值。

2

由于使用sklearn可能有点过头了,而且要消除需要男性和女性数据具有相同数量数据点的条件,因此这里使用numpy.polyfit实现线性拟合。同样的实现也证明了他们的方法并不是解决这个问题的方案。

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt

#data import
male = pd.read_csv("test1.txt", delim_whitespace=True)
female = pd.read_csv("test2.txt", delim_whitespace=True)

#linear fit of both populations
pmale   = np.polyfit(male.Year, male.Time, 1)
pfemale = np.polyfit(female.Year, female.Time, 1)

#more appealing presentation, let's pretend we do not just fit a line
x_fitmin=min(male.Year.min(), female.Year.min())
x_fitmax=max(male.Year.max(), female.Year.max())
x_fit=np.linspace(x_fitmin, x_fitmax, 100)

#create functions for the three fit lines
male_fit   = np.poly1d(pmale)
print(male_fit)
female_fit = np.poly1d(pfemale)
print(female_fit)
sep        = np.poly1d(np.mean([pmale, pfemale], axis=0))
print(sep)

#plot all markers and lines
ax = male.plot(x="Year", y="Time", c="blue", kind="scatter", label="male")
female.plot(x="Year", y="Time", c="red", kind="scatter", ax=ax, label="female")
ax.plot(x_fit, male_fit(x_fit), c="blue", ls="dotted", label="male fit")
ax.plot(x_fit, female_fit(x_fit), c="red", ls="dotted", label="female fit")
ax.plot(x_fit, sep(x_fit), c="black", ls="dashed", label="separator")

plt.legend()
plt.show()

示例输出:

-0.01333 x + 36.42
 
-0.01507 x + 40.92
 
-0.0142 x + 38.67

enter image description here

有一点仍然在错误的部分。然而,我认为这个问题非常有趣,因为我期望来自sklearn人群的回答适用于非线性数据组。我甚至安装了sklearn以期待解决!如果在接下来的几天里没有人发布一个好的解决方案,我将在此问题上设置赏金。


谢谢你的回答,但是在左端点上方有一个蓝色的点! - user
1
我说它复制了Chadd Robertson提出的内容,证明他们的方法并不是解决问题的方案,以免你在为数据实现通用方法而付出所有这些麻烦时感到失望。我在我的帖子中已经明确表达了这一点。 - Mr. T
1
我完全同意这个观点。然而,需要注意的是,在比较两组随机数据时,希望有一条直线完美地将它们分割开来并不总是可行的(这就是为什么我在第二张图中将数据点更紧密地分组的原因)。但是OmG的答案似乎考虑到了这一点——绝对是一个更好的方法。 - ChaddRobertson
1
在发布此问题后,我尝试了凸包方法。但是,看起来像一个简单的任务——分离两个明显不同的凸包图形——却变得更难以形式化。老实说,我浪费了大部分时间来理解几乎没有文档记录的“qhull”功能。但是,我喜欢这个问题,因为它让我走进了我很少涉足的领域。如果我想出了完整的“qhull”解决方案(OmG在他那个出色的SVM方法中省略了困难的部分,并从中取得了方程),我会事后发布的。 - Mr. T
这将不胜感激,这个看似简单的问题实际上比我最初想象的要更加复杂。现在考虑起来,凸包方法似乎是最好的起点 - 我曾试着制作周长并从中投射法线向量,但现已放置在一旁。 - ChaddRobertson

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