等頻離散法 根據數據的頻率分佈進行排序,然後按照頻率進行離散,好處是數據變為均勻分佈,但是會更改原有的數據結構。區間的邊界值要經過選擇,使得每個區間包含大致相等的實例數量。比如說 N=10 , 每個區間應該包含大約 10% 的實例。 Python 實現方式 等頻法是將相同數量的記錄放在每個區間,保證 ...
目錄
等頻離散法
根據數據的頻率分佈進行排序,然後按照頻率進行離散,好處是數據變為均勻分佈,但是會更改原有的數據結構。區間的邊界值要經過選擇,使得每個區間包含大致相等的實例數量。比如說 N=10 , 每個區間應該包含大約 10% 的實例。
Python 實現方式
等頻法是將相同數量的記錄放在每個區間,保證每個區間的數量基本一致。即將屬性值分為具有相同寬度的區間,區間的個數 boxSize 根據實際情況來決定。比如有 60 個樣本,我們要將其分為 boxSize=10 部分,則每部分的長度為 6 個樣本。其缺點是邊界易出現重覆值,如果為了刪除重覆值可以設置 duplicates=‘drop’,但易出現於分片個數少於指定個數的問題
import pandas as pd
import numpy as np
data = np.random.randint(1,100,200)
dd = pd.qcut(data, 10)
print(str(dd.value_counts()))
運行結果:
Java 實現方式
使用 Java 實現 Python 中的 qcut()
等頻分箱後的 value_counts()
功能。
/**
* 等頻分箱
*
* @param dataList 數據列表
* @param boxSize 分箱大小
*/
public static String quantileBasedDiscretion(List<BigDecimal> dataList, int boxSize) {
if (CollectionUtils.isEmpty(dataList) || 1 == dataList.size()) {
throw new RuntimeException("data set can not be null or size 1");
} // 數據集排序
dataList = dataList.stream().sorted().map(value -> value.setScale(3, RoundingMode.HALF_UP))
.collect(Collectors.toList());
BigDecimal[] boxValue = new BigDecimal[boxSize + 1];
// 獲取分位數對應的值
for (int i = 0; i <= boxSize; i++) {
double quantile = 1.0 * i / boxSize;
boxValue[i] = sortedListPercentile(dataList, quantile).setScale(3, RoundingMode.HALF_DOWN);
} // 重新計算首部分箱值
boxValue[0] = boxValue[0].multiply(BigDecimal.valueOf(1 - 1e-10)).setScale(3, RoundingMode.FLOOR);
if (boxValue[0].compareTo(BigDecimal.ZERO) <= 0) {
boxValue[0] = boxValue[0].add(new BigDecimal("-0.001")).setScale(3, RoundingMode.FLOOR);
} // 分箱操作
Map<String, Long> countMap = new LinkedHashMap<>();
for (int right = 1; right < boxValue.length; right++) {
int left = right - 1;
for (BigDecimal data : dataList) {
String key = "(" + boxValue[left] + ", " + boxValue[right] + "]";
if (data.compareTo(boxValue[left]) > 0 && data.compareTo(boxValue[right]) <= 0) {
countMap.compute(key, (k, value) -> value == null ? 1L : value + 1L);
}
countMap.putIfAbsent(key, 0L);
}
}
StringBuilder result = new StringBuilder();
countMap.forEach((key, value) -> result.append(key).append("\t").append(value).append("\n"));
// countMap.forEach((key, value) -> result.append(key).append(" ").append(value).append("|"));
return result.length() > 0 ? result.substring(0, result.length() - 1) : null;
}
/**
* 獲取指定分位數的值
*
* @param numList 數據列表(排序後)
* @param p 分位點
*/
public static BigDecimal sortedListPercentile(List<BigDecimal> numList, double p) {
int n = numList.size();
BigDecimal px = BigDecimal.valueOf(p).multiply(BigDecimal.valueOf(n - 1));
BigDecimal i = px.setScale(0, RoundingMode.FLOOR);
BigDecimal g = px.subtract(i);
if (g.compareTo(BigDecimal.ZERO) == 0) {
return numList.get(i.intValue());
} else {
BigDecimal d1 = BigDecimal.ONE.subtract(g).multiply(numList.get(i.intValue())).setScale(2, RoundingMode.HALF_UP);
BigDecimal d2 = g.multiply(numList.get(i.intValue() + 1)).setScale(2, RoundingMode.HALF_UP);
return d1.add(d2);
}
}
運行結果:
測試結果對比
100 次運行結果差異對比
10000 次運行結果差異對比
ps: 這裡的分箱成功率使用的是每個區間分的的值作為判斷依據。
由上圖可以得出在 1 萬條數據時,存在 22% 的數據與 Python 分箱後的值存在差異。但通過區間數據可以觀測出統計範圍誤差在可容忍範圍 (0.000 ~ 0.099) 內,且最終統計數據值之和均為相同的,不存在數據未分區的情況。
總結
這是一個簡易的 Java 等頻分箱功能實現。對比 Python 的 Pandas 庫中 qcut()
方法還有很大的提升空間。包括如果邊界出現重覆值的問題處理等問題還未實現。功能實現僅供參考,希望能給大家帶來幫助,也歡迎相互學習討論~
參考文獻:
Java 分位點 (分位值) 計算_0xYGC 的博客 - CSDN 博客_分位點怎麼計算
python 數據分析之數據離散化 —— 等寬 & 等頻 & 聚類離散_Mr 番茄蛋的博客 - CSDN 博客_python 數據離散化