在 python 中,如何在考虑 class 的情况下使用精度作为标准对连续变量进行离散化
In python, how to discretize continuous variable using accuracy as a criterion taking class into consideration
对于一组受试者,我有一个范围为 0-100 的连续变量,表示受试者状态的量化 cont_attribute
。对于每个主题,我还有一个序数变量,表示 reader 将主题状态注释为四种状态之一(例如 1、2、3、4)class_label
。 cont_attribute
的值在 class 之间重叠。我的目标是离散化 cont_attribute
以便优化与 class 的一致性。
当离散化cont_attribute
时,任意阈值x1
、x2
、x3
可以直接应用于连续变量,以产生四个有序类别和一致的bin用reader注解class可以评估:
cohen_kappa_score((pd.cut(df['cont_attribute'],bins=[0, x1, x2, x3, 100], labels=['1','2','3','4']).astype('int'))
, df['class_label'].astype('int'))
我找到了几个连续变量离散化的选项,例如 Jenks natural breaks 和 sklearn Kmeans,尽管这些选项不采用考虑到 class.
我试过的:
我尝试优化上面的函数以使用 scipy.optimize.minimize 产生最大值。这里对于两个 classes 之间的每个阈值,我使用较大的 class 的最小值和较小的 classes 的最大值作为找到各自最佳截止值的范围那些 classes 之间的点。用这个方法我运行遇到了问题,提示:
ValueError: bins must increase monotonically.
def objfunc(grid):
x1, x2, x3 = grid
return (cohen_kappa_score((pd.cut(df.cont_attribute,bins=[0, x1, x2, x3, 100],labels=['1','2','3','4'], duplicates='drop').astype('int'))
, df['class_label'].astype('int'))) * (-1);
grid = (slice(df[(df['class_label'] == 2)]['cont_attribute'].min(), df[(df['class_label'] == 1)]['cont_attribute'].max(), 0.5), (slice(df[(df['class_label'] == 3)]['cont_attribute'].min(), df[(df['class_label'] == 2)]['cont_attribute'].max(), 0.5), (slice(df[(df['class_label'] == 4)]['cont_attribute'].min(), df[(df['class_label'] == 3)]['cont_attribute'].max(), 0.5))
solution = brute(objfunc, grid, finish=None,full_output = True)
solution
在 python 中,是否有一种直接的方法来优化阈值 x1
、x2
、x3
并考虑与 class 的一致性(监督离散化)?或者,如何使用 scipy.optimize.minimize?
重写上述函数以产生最大值
错误信息并不太难。 pandas cut
方法要求切割向量
[0,x1,x2,x3,100]
是严格单调的。通过某种机制来确保没有无效值被传递给 cut
函数,我们是安全的。这就是我在下面实现的。为了表示 invalid
设置,习惯上使用 np.inf
因为所有其他值都较低。因此,每个 minizmier 都会说这样一个无效的解决方案是不可取的。见下文的实施。我还包含了所有导入和一些数据生成,因此使用代码很简单。请在以后的问题中也这样做。
您可能希望在强力搜索中每个维度使用 10 个以上的分箱。
另外 - 代码效率很低。由于它对 x1、x2、x3 的所有组合进行暴力破解,但其中很多组合都是无效的(例如 x2<=x1),因此您可能希望在 (x1,x2-x1, x3-x2) 中参数化问题,并在第二个和第三个分量中搜索非负值。
最后,brute
方法是一个最小化器,所以你应该 return -cohen_kappa
来自 objective
#%%
import numpy as np
from sklearn.metrics import cohen_kappa_score, confusion_matrix
from scipy.stats import truncnorm
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.optimize import brute
#
# Generate Data
#
n = 1000
np.random.seed(0)
y = np.random.choice(4, p=[0.1, 0.3, 0.4, 0.2], size=n)
x = np.zeros(n)
for i in range(5):
low = 0
high = 100
mymean = 20 * i
myscale = 8
a, b = (low - mymean) / myscale, (high - mymean) / myscale
x[y == i] = truncnorm.rvs(a=a, b=b, loc=mymean, scale=myscale, size=np.sum(y == i))
data = pd.DataFrame({"cont_attribute": x, "class_label": y})
# make a loss function that accounts for the bad orderings
def loss(cuts):
x1, x2, x3 = cuts
if 0 >= x1 or x1 >= x2 or x2 >= x3 or x3 >= 100:
return np.inf
yhat = pd.cut(
data["cont_attribute"],
bins=[0, x1, x2, x3, 100],
labels=[0, 1, 2, 3],
# duplicates="drop",
).astype("int")
return -cohen_kappa_score(data["class_label"], yhat)
# Compute the result via brute force
ranges = [(0, 100)] * 3
Ns=30
result = brute(func=loss, ranges=ranges, Ns=Ns)
print(result)
print(-loss(result))
# Evaluate the final result in a confusion matrix
x1, x2, x3 = result
data["class_pred"] = pd.cut(
data["cont_attribute"],
bins=[0, x1, x2, x3, 100],
labels=[0, 1, 2, 3],
duplicates="drop",
).astype("int")
mat = confusion_matrix(y_true=data['class_label'],y_pred=data['class_pred'])
plt.matshow(mat)
# Loop over data dimensions and create text annotations.
for i in range(4):
for j in range(4):
text = plt.text(j, i, mat[i, j],
ha="center", va="center", color="grey")
plt.xlabel('Predicted class')
plt.ylabel('True class')
plt.show()
# Evaluate result graphically
# inspect the data
fig,ax = plt.subplots(2,1)
sns.histplot(data=data, x="cont_attribute", hue="class_label",ax=ax[0],multiple='stack')
sns.histplot(data=data, x="cont_attribute", hue="class_pred",ax=ax[1],multiple='stack')
plt.show()
关于 scipy.optimize.minimize
的使用,当使用 cohen kappa 作为目标时,这是不可能的。由于不可微,因此优化起来并不容易。考虑改用交叉熵损失函数。但在那种情况下,您需要一个(参数)模型来执行分类任务。
statsmodels
中的序数回归包中提供了标准序数分类器。它将比粗暴方法快得多,但在 cohen kappa 上评估时可能不太准确。如果要获得更多的垃圾箱,我可能会走那条路。
对于一组受试者,我有一个范围为 0-100 的连续变量,表示受试者状态的量化 cont_attribute
。对于每个主题,我还有一个序数变量,表示 reader 将主题状态注释为四种状态之一(例如 1、2、3、4)class_label
。 cont_attribute
的值在 class 之间重叠。我的目标是离散化 cont_attribute
以便优化与 class 的一致性。
当离散化cont_attribute
时,任意阈值x1
、x2
、x3
可以直接应用于连续变量,以产生四个有序类别和一致的bin用reader注解class可以评估:
cohen_kappa_score((pd.cut(df['cont_attribute'],bins=[0, x1, x2, x3, 100], labels=['1','2','3','4']).astype('int'))
, df['class_label'].astype('int'))
我找到了几个连续变量离散化的选项,例如 Jenks natural breaks 和 sklearn Kmeans,尽管这些选项不采用考虑到 class.
我试过的:
我尝试优化上面的函数以使用 scipy.optimize.minimize 产生最大值。这里对于两个 classes 之间的每个阈值,我使用较大的 class 的最小值和较小的 classes 的最大值作为找到各自最佳截止值的范围那些 classes 之间的点。用这个方法我运行遇到了问题,提示:
ValueError: bins must increase monotonically.
def objfunc(grid):
x1, x2, x3 = grid
return (cohen_kappa_score((pd.cut(df.cont_attribute,bins=[0, x1, x2, x3, 100],labels=['1','2','3','4'], duplicates='drop').astype('int'))
, df['class_label'].astype('int'))) * (-1);
grid = (slice(df[(df['class_label'] == 2)]['cont_attribute'].min(), df[(df['class_label'] == 1)]['cont_attribute'].max(), 0.5), (slice(df[(df['class_label'] == 3)]['cont_attribute'].min(), df[(df['class_label'] == 2)]['cont_attribute'].max(), 0.5), (slice(df[(df['class_label'] == 4)]['cont_attribute'].min(), df[(df['class_label'] == 3)]['cont_attribute'].max(), 0.5))
solution = brute(objfunc, grid, finish=None,full_output = True)
solution
在 python 中,是否有一种直接的方法来优化阈值 x1
、x2
、x3
并考虑与 class 的一致性(监督离散化)?或者,如何使用 scipy.optimize.minimize?
错误信息并不太难。 pandas cut
方法要求切割向量
[0,x1,x2,x3,100]
是严格单调的。通过某种机制来确保没有无效值被传递给 cut
函数,我们是安全的。这就是我在下面实现的。为了表示 invalid
设置,习惯上使用 np.inf
因为所有其他值都较低。因此,每个 minizmier 都会说这样一个无效的解决方案是不可取的。见下文的实施。我还包含了所有导入和一些数据生成,因此使用代码很简单。请在以后的问题中也这样做。
您可能希望在强力搜索中每个维度使用 10 个以上的分箱。
另外 - 代码效率很低。由于它对 x1、x2、x3 的所有组合进行暴力破解,但其中很多组合都是无效的(例如 x2<=x1),因此您可能希望在 (x1,x2-x1, x3-x2) 中参数化问题,并在第二个和第三个分量中搜索非负值。
最后,brute
方法是一个最小化器,所以你应该 return -cohen_kappa
来自 objective
#%%
import numpy as np
from sklearn.metrics import cohen_kappa_score, confusion_matrix
from scipy.stats import truncnorm
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.optimize import brute
#
# Generate Data
#
n = 1000
np.random.seed(0)
y = np.random.choice(4, p=[0.1, 0.3, 0.4, 0.2], size=n)
x = np.zeros(n)
for i in range(5):
low = 0
high = 100
mymean = 20 * i
myscale = 8
a, b = (low - mymean) / myscale, (high - mymean) / myscale
x[y == i] = truncnorm.rvs(a=a, b=b, loc=mymean, scale=myscale, size=np.sum(y == i))
data = pd.DataFrame({"cont_attribute": x, "class_label": y})
# make a loss function that accounts for the bad orderings
def loss(cuts):
x1, x2, x3 = cuts
if 0 >= x1 or x1 >= x2 or x2 >= x3 or x3 >= 100:
return np.inf
yhat = pd.cut(
data["cont_attribute"],
bins=[0, x1, x2, x3, 100],
labels=[0, 1, 2, 3],
# duplicates="drop",
).astype("int")
return -cohen_kappa_score(data["class_label"], yhat)
# Compute the result via brute force
ranges = [(0, 100)] * 3
Ns=30
result = brute(func=loss, ranges=ranges, Ns=Ns)
print(result)
print(-loss(result))
# Evaluate the final result in a confusion matrix
x1, x2, x3 = result
data["class_pred"] = pd.cut(
data["cont_attribute"],
bins=[0, x1, x2, x3, 100],
labels=[0, 1, 2, 3],
duplicates="drop",
).astype("int")
mat = confusion_matrix(y_true=data['class_label'],y_pred=data['class_pred'])
plt.matshow(mat)
# Loop over data dimensions and create text annotations.
for i in range(4):
for j in range(4):
text = plt.text(j, i, mat[i, j],
ha="center", va="center", color="grey")
plt.xlabel('Predicted class')
plt.ylabel('True class')
plt.show()
# Evaluate result graphically
# inspect the data
fig,ax = plt.subplots(2,1)
sns.histplot(data=data, x="cont_attribute", hue="class_label",ax=ax[0],multiple='stack')
sns.histplot(data=data, x="cont_attribute", hue="class_pred",ax=ax[1],multiple='stack')
plt.show()
关于 scipy.optimize.minimize
的使用,当使用 cohen kappa 作为目标时,这是不可能的。由于不可微,因此优化起来并不容易。考虑改用交叉熵损失函数。但在那种情况下,您需要一个(参数)模型来执行分类任务。
statsmodels
中的序数回归包中提供了标准序数分类器。它将比粗暴方法快得多,但在 cohen kappa 上评估时可能不太准确。如果要获得更多的垃圾箱,我可能会走那条路。