二手車網(wǎng)站建站海豹直播nba
1.項目背景
牛油果(Avocado)作為全球廣受歡迎的水果之一,其成熟度直接影響口感、營養(yǎng)和市場價值。Hass牛油果因其獨特的外皮和細膩的果肉成為最常見的商業(yè)品種,牛油果的成熟過程伴隨著果實硬度、顏色、重量、體積等物理特性的變化,準確判斷成熟度對于供應(yīng)鏈管理、消費者體驗和自動化分揀具有重要意義。
本項目通過統(tǒng)計檢驗識別判斷牛油果的成熟度的因素,并且通過構(gòu)建機器學(xué)習(xí)模型,能夠準確預(yù)測牛油果的成熟度,雖然數(shù)據(jù)是合成數(shù)據(jù),但是分析的過程是可以復(fù)現(xiàn)的。
2.數(shù)據(jù)說明
字段 | 說明 |
---|---|
firmness | 果實硬度,穿透阻力(牛油果的硬度,單位N) |
hue | 果皮主色調(diào)(色相,0-360°) |
saturation | 色彩飽和度(0-100%) |
brightness | 亮度(0-100%) |
color_category | 視覺顏色分類(如深綠、紫色、黑色等) |
sound_db | 敲擊聲學(xué)響應(yīng)(分貝,30-80dB) |
weight_g | 果實質(zhì)量(克,150-300g) |
size_cm3 | 果實體積(立方厘米,100-300cm3) |
ripeness | 成熟度分級標簽(hard(生硬), pre-conditioned(預(yù)熟), breaking(轉(zhuǎn)熟), firm-ripe(硬熟), ripe(成熟)) |
3.Python庫導(dǎo)入及數(shù)據(jù)讀取
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import mannwhitneyu,chi2_contingency,kruskal
import itertools
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
data = pd.read_csv('/home/mw/input/data3657/avocado_ripeness_dataset.csv')
4.數(shù)據(jù)預(yù)覽及預(yù)處理
data.head()
firmness | hue | saturation | brightness | color_category | sound_db | weight_g | size_cm3 | ripeness | |
---|---|---|---|---|---|---|---|---|---|
0 | 14.5 | 19 | 40 | 26 | black | 34 | 175 | 261 | ripe |
1 | 71.7 | 53 | 69 | 75 | green | 69 | 206 | 185 | pre-conditioned |
2 | 88.5 | 60 | 94 | 46 | dark green | 79 | 220 | 143 | hard |
3 | 93.8 | 105 | 87 | 41 | dark green | 75 | 299 | 140 | hard |
4 | 42.5 | 303 | 58 | 32 | purple | 63 | 200 | 227 | breaking |
print('查看數(shù)據(jù)信息:')
data.info()
查看數(shù)據(jù)信息:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 250 entries, 0 to 249
Data columns (total 9 columns):# Column Non-Null Count Dtype
--- ------ -------------- ----- 0 firmness 250 non-null float641 hue 250 non-null int64 2 saturation 250 non-null int64 3 brightness 250 non-null int64 4 color_category 250 non-null object 5 sound_db 250 non-null int64 6 weight_g 250 non-null int64 7 size_cm3 250 non-null int64 8 ripeness 250 non-null object
dtypes: float64(1), int64(6), object(2)
memory usage: 17.7+ KB
print(f'查看重復(fù)值:{data.duplicated().sum()}')
查看重復(fù)值:0
characteristic = ['color_category','ripeness']
print('數(shù)據(jù)中顏色和成熟度標簽的唯一值情況:')
for i in characteristic:print(f'{i}:')print(f'共有:{len(data[i].unique())}條唯一值')print(data[i].unique())print('-'*50)
數(shù)據(jù)中顏色和成熟度標簽的唯一值情況:
color_category:
共有:4條唯一值
['black' 'green' 'dark green' 'purple']
--------------------------------------------------
ripeness:
共有:5條唯一值
['ripe' 'pre-conditioned' 'hard' 'breaking' 'firm-ripe']
--------------------------------------------------
顏色的話分別是:黑色、綠色、深綠色、紫色;成熟標簽的話分別是成熟、預(yù)熟、生硬、轉(zhuǎn)熟、硬熟,我查閱了一下,牛油果分為五個階段,如下圖所示(圖片來源于:https://mbd.baidu.com/newspage/data/dtlandingsuper?nid=dt_3977908945549881010&sourceFrom=search_a)
在數(shù)據(jù)集中,順序為:hard ?pre-conditioned?breaking?firm-ripe?ripe,這些分級有助于供應(yīng)鏈管理、自動分揀和消費者選購。
feature_map = {'firmness': '果實硬度','hue': '色相','saturation': '飽和度','brightness': '亮度','sound_db': '敲擊聲分貝','weight_g': '重量','size_cm3': '體積'
}unit_map = {'firmness': '(N)','hue': '(°)', 'saturation': '(%)', 'brightness': '(%)', 'sound_db': '(dB)','weight_g': '(g)','size_cm3': '(cm$^3$)'
}# 子圖標簽
labels = ['(a)', '(b)', '(c)', '(d)', '(e)', '(f)', '(g)']fig = plt.figure(figsize=(20, 10))boxprops = {'edgecolor': 'black', 'facecolor': 'white', 'linewidth': 1.5}
whiskerprops = {'color': 'black', 'linewidth': 1.5}
capprops = {'color': 'black', 'linewidth': 1.5}
medianprops = {'color': 'black', 'linewidth': 2}
flierprops = {'marker': 'o', 'markerfacecolor': 'white', 'markeredgecolor': 'black', 'markersize': 6}for i, (col, col_name) in enumerate(feature_map.items(), 1):ax = fig.add_subplot(2, 4, i)# 使用自定義樣式繪制箱線圖sns.boxplot(y=data[col], ax=ax,boxprops=boxprops,whiskerprops=whiskerprops,capprops=capprops,medianprops=medianprops,flierprops=flierprops,width=0.5)ax.set_title(f'{labels[i-1]} {col_name}的分布')ax.set_ylabel(f'{col_name}{unit_map[col]}')# 設(shè)置網(wǎng)格線ax.grid(axis='y', linestyle='--', alpha=0.3)# 移除上邊框和右邊框ax.spines['top'].set_visible(False)ax.spines['right'].set_visible(False)# 加粗剩余的邊框ax.spines['bottom'].set_linewidth(1.5)ax.spines['left'].set_linewidth(1.5)# 為整個圖添加一個總標題
fig.suptitle('圖 1 牛油果特征的異常值檢驗', fontsize=16, y=0.98)# 調(diào)整布局,確保圖表不重疊
plt.tight_layout(rect=[0, 0, 1, 0.96]) # rect參數(shù)留出頂部空間給總標題
plt.show()

數(shù)據(jù)比較干凈,可以直接開始分析了。
5.描述性分析
data.describe(include='all')
firmness | hue | saturation | brightness | color_category | sound_db | weight_g | size_cm3 | ripeness | |
---|---|---|---|---|---|---|---|---|---|
count | 250.000000 | 250.00000 | 250.000000 | 250.00000 | 250 | 250.000000 | 250.000000 | 250.000000 | 250 |
unique | NaN | NaN | NaN | NaN | 4 | NaN | NaN | NaN | 5 |
top | NaN | NaN | NaN | NaN | black | NaN | NaN | NaN | ripe |
freq | NaN | NaN | NaN | NaN | 75 | NaN | NaN | NaN | 50 |
mean | 50.618400 | 125.94400 | 64.356000 | 45.07200 | NaN | 58.016000 | 220.188000 | 208.644000 | NaN |
std | 27.277678 | 117.13767 | 17.377144 | 19.11629 | NaN | 13.838126 | 34.405225 | 55.940564 | NaN |
min | 10.300000 | 1.00000 | 30.000000 | 10.00000 | NaN | 30.000000 | 152.000000 | 100.000000 | NaN |
25% | 25.750000 | 25.25000 | 51.250000 | 31.00000 | NaN | 47.250000 | 193.250000 | 155.250000 | NaN |
50% | 48.950000 | 77.00000 | 65.000000 | 46.00000 | NaN | 60.000000 | 220.000000 | 218.000000 | NaN |
75% | 74.050000 | 278.75000 | 76.750000 | 58.00000 | NaN | 68.000000 | 245.000000 | 260.500000 | NaN |
max | 98.800000 | 329.00000 | 99.000000 | 78.00000 | NaN | 79.000000 | 299.000000 | 299.000000 | NaN |
fig = plt.figure(figsize=(20, 10))# 子圖標簽列表 - 使用標準格式
labels = ['(a)', '(b)', '(c)', '(d)', '(e)', '(f)', '(g)', '(h)']# 1. 果實硬度分布 (1,1)
ax1 = fig.add_subplot(2, 4, 1)
ax1.hist(data['firmness'], bins=15, edgecolor='black', color='white', linewidth=1.5)
ax1.set_title(f'{labels[0]} 果實硬度的頻率分布')
ax1.set_xlabel('果實硬度 (N)')
ax1.set_ylabel('頻數(shù)')
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
ax1.spines['bottom'].set_linewidth(1.5) # 加粗下邊框
ax1.spines['left'].set_linewidth(1.5) # 加粗左邊框# 2. 色相分布 - 使用極坐標 (1,2)
ax2 = fig.add_subplot(2, 4, 2, projection='polar')
hue_rad = np.deg2rad(data['hue'])
hist, bins = np.histogram(hue_rad, bins=36)
bin_centers = (bins[:-1] + bins[1:]) / 2
ax2.bar(bin_centers, hist, width=np.diff(bins), color='white', edgecolor='black', linewidth=1.5)
ax2.set_title(f'{labels[1]} 色相的極坐標分布')
# 為極坐標圖添加角度標簽
angles = np.arange(0, 360, 30)
ax2.set_thetagrids(angles, labels=[f'{angle}°' for angle in angles])
ax2.grid(True, linestyle='--', alpha=0.5)# 3. 飽和度分布 (1,3)
ax3 = fig.add_subplot(2, 4, 3)
ax3.hist(data['saturation'], bins=15, edgecolor='black', color='white', linewidth=1.5)
ax3.set_title(f'{labels[2]} 飽和度的頻率分布')
ax3.set_xlabel('飽和度 (%)')
ax3.set_ylabel('頻數(shù)')
ax3.spines['top'].set_visible(False)
ax3.spines['right'].set_visible(False)
ax3.spines['bottom'].set_linewidth(1.5)
ax3.spines['left'].set_linewidth(1.5)# 4. 亮度分布 (1,4)
ax4 = fig.add_subplot(2, 4, 4)
ax4.hist(data['brightness'], bins=15, edgecolor='black', color='white', linewidth=1.5)
ax4.set_title(f'{labels[3]} 亮度的頻率分布')
ax4.set_xlabel('亮度 (%)')
ax4.set_ylabel('頻數(shù)')
ax4.spines['top'].set_visible(False)
ax4.spines['right'].set_visible(False)
ax4.spines['bottom'].set_linewidth(1.5)
ax4.spines['left'].set_linewidth(1.5)# 5. 敲擊聲分貝分布 (2,1)
ax5 = fig.add_subplot(2, 4, 5)
ax5.hist(data['sound_db'], bins=15, edgecolor='black', color='white', linewidth=1.5)
ax5.set_title(f'{labels[4]} 敲擊聲分貝的頻率分布')
ax5.set_xlabel('分貝值 (dB)')
ax5.set_ylabel('頻數(shù)')
ax5.spines['top'].set_visible(False)
ax5.spines['right'].set_visible(False)
ax5.spines['bottom'].set_linewidth(1.5)
ax5.spines['left'].set_linewidth(1.5)# 6. 體積-質(zhì)量散點圖 (2,2)
ax6 = fig.add_subplot(2, 4, 6)
ax6.scatter(data['weight_g'], data['size_cm3'], edgecolor='black', facecolor='white', linewidth=1.5, alpha=0.7)
ax6.set_title(f'{labels[5]} 質(zhì)量與體積的相關(guān)性')
ax6.set_xlabel('質(zhì)量 (g)')
ax6.set_ylabel('體積 (cm$^3$)')
# 添加趨勢線
z = np.polyfit(data['weight_g'], data['size_cm3'], 1)
p = np.poly1d(z)
ax6.plot(data['weight_g'], p(data['weight_g']), "r--", linewidth=1.5, alpha=0.7)
ax6.spines['top'].set_visible(False)
ax6.spines['right'].set_visible(False)
ax6.spines['bottom'].set_linewidth(1.5)
ax6.spines['left'].set_linewidth(1.5)# 7. 顏色類別分布 (2,3)
ax7 = fig.add_subplot(2, 4, 7)
color_counts = data['color_category'].value_counts().sort_index() # 排序以保持一致
bars = ax7.bar(color_counts.index, color_counts.values, edgecolor='black', linewidth=1.5)
for bar in bars:bar.set_facecolor('white')
# 添加數(shù)值標簽
for bar in bars:height = bar.get_height()ax7.text(bar.get_x() + bar.get_width()/2., height,f'{int(height)}', ha='center', va='bottom')
ax7.set_title(f'{labels[6]} 顏色類別的頻率分布')
plt.setp(ax7.get_xticklabels(), rotation=90)
ax7.set_xlabel('顏色類別')
ax7.set_ylabel('頻數(shù)')
ax7.spines['top'].set_visible(False)
ax7.spines['right'].set_visible(False)
ax7.spines['bottom'].set_linewidth(1.5)
ax7.spines['left'].set_linewidth(1.5)# 8. 成熟度分布 (2,4)
ax8 = fig.add_subplot(2, 4, 8)
ripeness_counts = data['ripeness'].value_counts()
# 按成熟度級別排序
ripeness_order = ['hard', 'pre-conditioned', 'breaking', 'firm-ripe', 'ripe']
ripeness_sorted = pd.Series({k: ripeness_counts.get(k, 0) for k in ripeness_order})
bars = ax8.bar(ripeness_sorted.index, ripeness_sorted.values, edgecolor='black', linewidth=1.5)
for bar in bars:bar.set_facecolor('white')
# 添加數(shù)值標簽
for bar in bars:height = bar.get_height()ax8.text(bar.get_x() + bar.get_width()/2., height,f'{int(height)}', ha='center', va='bottom')
ax8.set_title(f'{labels[7]} 成熟度級別的頻率分布')
plt.setp(ax8.get_xticklabels(), rotation=90)
ax8.set_xlabel('成熟度級別')
ax8.set_ylabel('頻數(shù)')
ax8.spines['top'].set_visible(False)
ax8.spines['right'].set_visible(False)
ax8.spines['bottom'].set_linewidth(1.5)
ax8.spines['left'].set_linewidth(1.5)fig.suptitle('圖 2 牛油果特征分布與關(guān)聯(lián)性分析', fontsize=16, y=0.98)
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()

數(shù)值特征分布:
- 果實硬度: 平均50.6 N,主要分布在25.8-74.1 N區(qū)間
- 色相: 平均125.9°,分布不均勻,四分位差較大(25.3-278.8°)
- 飽和度: 平均64.4%,大部分集中在51.3-76.8%之間
- 亮度: 平均45.1%,分布范圍為10-78%
- 敲擊聲分貝: 平均58.0 dB,主要分布在47.3-68.0 dB區(qū)間
- 重量: 平均220.2 g,四分位范圍為193.3-245.0 g
- 體積: 平均208.6 cm3,分布范圍較廣(100-299 cm3)
分類特征分布:
- 顏色類別: black(75個)、dark green(50個)、green(50個)、purple(75個)
- 成熟度級別: 5個成熟度等級均為50個樣本(hard、pre-conditioned、breaking、firm-ripe、ripe)
數(shù)據(jù)是虛擬合成的數(shù)據(jù),存在比較不合理的地方,就是質(zhì)量越大,體積越小,這明顯是異常的,這明顯是數(shù)據(jù)作者沒考慮周到導(dǎo)致的。
6.統(tǒng)計檢驗
6.1Mann-Whitney U檢驗
plt.rcParams['font.size'] = 13
plt.rcParams['axes.titlesize'] = 14
plt.rcParams['axes.labelsize'] = 13
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12# 刪除色相
features = [k for k in feature_map.keys() if k != 'hue']
labels = ['(a)', '(b)', '(c)', '(d)', '(e)', '(f)']
n = len(features)
ncols = 3
nrows = 2fig, axes = plt.subplots(nrows, ncols, figsize=(20, 15))
axes = axes.flatten()for idx, feature in enumerate(features):ax = axes[idx]sns.boxplot(x='ripeness', y=feature, data=data, showfliers=True,order=ripeness_order,boxprops=dict(facecolor='white', edgecolor='black', linewidth=1.5),medianprops=dict(color='red', linewidth=1.5),whiskerprops=dict(color='black', linewidth=1.5),capprops=dict(color='black', linewidth=1.5),flierprops=dict(marker='o', color='black', alpha=0.5, markersize=5),linewidth=1.5,ax=ax)# 顯著性標注unique_groups = ripeness_order pairs = list(itertools.combinations(unique_groups, 2))y_max = data[feature].max()y_min = data[feature].min()y_offset = (y_max - y_min) * 0.08 if y_max > y_min else 1for i, (g1, g2) in enumerate(pairs):data1 = data[data['ripeness'] == g1][feature].dropna()data2 = data[data['ripeness'] == g2][feature].dropna()# 跳過無數(shù)據(jù)的分組if len(data1) == 0 or len(data2) == 0:continuestat, p = mannwhitneyu(data1, data2, alternative='two-sided')x1, x2 = unique_groups.index(g1), unique_groups.index(g2)y = y_max + y_offset * (i+1)ax.plot([x1, x1, x2, x2], [y, y+0.5, y+0.5, y], lw=1.5, c='black')# 星號表示法if p < 0.0001:stars = '****'elif p < 0.001:stars = '***'elif p < 0.01:stars = '**'elif p < 0.05:stars = '*'else:stars = 'ns'ax.text((x1 + x2) / 2, y + 0.5, stars, ha='center', va='bottom', color='black', fontsize=13)# 設(shè)置標簽和標題ax.set_xlabel('成熟度', fontsize=13)ax.set_ylabel(f"{feature_map[feature]} {unit_map[feature]}", fontsize=13)ax.set_title(f"{labels[idx]} 不同成熟度下{feature_map[feature]}分布", fontsize=14)ax.spines['top'].set_visible(False)ax.spines['right'].set_visible(False)ax.spines['bottom'].set_linewidth(1.5)ax.spines['left'].set_linewidth(1.5)ax.tick_params(axis='x', labelrotation=90)# 多余的子圖隱藏
for j in range(n, nrows*ncols):fig.delaxes(axes[j])fig.suptitle('圖 3 牛油果特征不同成熟度下分布及Mann-Whitney U檢驗', fontsize=16, y=0.98)
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()

- 果實硬度:果實硬度在不同成熟度分級間存在極顯著差異,隨著成熟度的增加,果實硬度逐漸降低。
- 飽和度:飽和度在不同成熟度分級間存在極顯著差異,隨著成熟度的增加,飽和度逐漸降低。
- 亮度:飽和度在不同成熟度分級間存在顯著差異,預(yù)熟的時候亮度最高,隨著成熟度增加,亮度逐漸降低。
- 敲擊聲分貝:敲擊聲分貝在不同成熟度分級間存在極顯著差異,隨著成熟度增加,敲擊聲分貝越低,與果實硬度有關(guān)。
- 重量:重量在不同成熟度分級間存在顯著差異,隨著成熟度增加,重量越低,硬熟期和成熟期的差異不顯著。
- 體積:體積在不同成熟度分級間存在極顯著差異,隨著成熟度增加,體積越大。
這里色相不能使用Mann-Whitney U檢驗,因為是環(huán)狀變量,0°和360°等價,不能直接用普通的線性統(tǒng)計方法,通過詢問AI,AI給了我一個方法:圓形統(tǒng)計(Circular Statistics)。
圓形統(tǒng)計是專門用于分析方向性數(shù)據(jù)或周期性數(shù)據(jù)(如角度、時間、方位、色相等)的統(tǒng)計分支,它與常規(guī)的線性統(tǒng)計方法有本質(zhì)區(qū)別,因為圓形數(shù)據(jù)的“頭尾相接”特性(如0°和360°是同一個點)。
1. 圓形數(shù)據(jù)的典型場景
- 角度:風向、動物遷徙方向、地震方位等
- 時間:一天24小時(0點和24點是同一時刻)
- 色相:色輪上的色相(0°和360°等價)
- 生物節(jié)律:如晝夜節(jié)律、季節(jié)性變化
2. 圓形統(tǒng)計的基本概念
(1)均值和方差
- 圓形均值:不是簡單的算術(shù)平均,而是將每個角度轉(zhuǎn)為單位向量(sin/cos分量),求向量和的方向。
- 圓形方差/標準差:反映數(shù)據(jù)在圓上的分散程度。
(2)距離和差異
- 圓形距離:兩個角度的最短弧長(如5°和355°的距離是10°,不是350°)。
(3)分布
- 常見分布:如均勻分布、von Mises分布(圓形上的“正態(tài)分布”)。
3. 圓形統(tǒng)計的常用指標
- 圓形均值方向(mean direction):
θ ˉ = arctan ? 2 ( ∑ sin ? θ i , ∑ cos ? θ i ) \bar{\theta} = \arctan2(\sum \sin\theta_i, \sum \cos\theta_i) θˉ=arctan2(∑sinθi?,∑cosθi?) - 均向長度(mean resultant length, R):
R = ( ∑ cos ? θ i ) 2 + ( ∑ sin ? θ i ) 2 / n R = \sqrt{(\sum \cos\theta_i)^2 + (\sum \sin\theta_i)^2} / n R=(∑cosθi?)2+(∑sinθi?)2?/n
R越接近1,數(shù)據(jù)越集中;越接近0,數(shù)據(jù)越分散。 - 圓形方差: 1 ? R 1 - R 1?R
- 圓形標準差: ? 2 ln ? R \sqrt{-2 \ln R} ?2lnR?
4. 圓形統(tǒng)計的常用檢驗方法
- Rayleigh檢驗:檢驗數(shù)據(jù)是否均勻分布在圓上(無偏向性)。
- Watson-Williams檢驗:多組均值方向的方差分析(要求分布近似von Mises)。
- Mardia-Watson-Wheeler檢驗:多組分布的非參數(shù)檢驗(類似Kruskal-Wallis,但針對圓形數(shù)據(jù))。
- Kuiper檢驗、Watson U2檢驗:分布一致性檢驗,類似于Kolmogorov-Smirnov檢驗但適用于圓形數(shù)據(jù)。
一般使用R的circular
包來實現(xiàn),Python庫都存在局限性,這里AI給了我平替的方法:Kruskal-Wallis 檢驗,后續(xù)會展開介紹。
5. 圓形統(tǒng)計與線性統(tǒng)計的區(qū)別
線性統(tǒng)計 | 圓形統(tǒng)計 |
---|---|
0和最大值不相等 | 0和最大值相等 |
均值=算術(shù)平均 | 均值=向量平均 |
距離=絕對差 | 距離=最短弧長 |
方差=平方差平均 | 方差=1-R |
6. 參考資料
- R包文檔:circular: Circular Statistics
6.2Kruskal-Wallis 檢驗
Kruskal-Wallis 檢驗在色相(Hue)分析中的應(yīng)用
1. Kruskal-Wallis 檢驗簡介
- Kruskal-Wallis 檢驗是非參數(shù)的方差分析(ANOVA),用于比較三個及以上獨立樣本的中位數(shù)是否有顯著差異。
- 適用于不服從正態(tài)分布的數(shù)據(jù)。
- 其本質(zhì)是將所有數(shù)據(jù)排序后,比較各組的秩和。
2. 色相的Kruskal-Wallis檢驗思路
因為色相是環(huán)狀的,直接用角度會導(dǎo)致0°和360°被人為拉遠,失去真實的距離關(guān)系,所以將色相角度(度)轉(zhuǎn)為弧度,然后分別計算其正弦(sin) 和余弦(cos) 分量,色相的環(huán)狀特性就被保留在sin/cos的周期性變化中,對sin分量和cos分量分別做Kruskal-Wallis檢驗,如果任一分量有顯著差異,說明組間色相分布有差異。
# 角度轉(zhuǎn)弧度
data['hue_rad'] = np.deg2rad(data['hue'])groups = ['hard', 'pre-conditioned', 'breaking', 'firm-ripe', 'ripe']
hue_rad_groups = [data.loc[data['ripeness'] == g, 'hue_rad'].dropna() for g in groups]# 對sin分量做Kruskal-Wallis檢驗
sin_groups = [np.sin(g) for g in hue_rad_groups]
stat_sin, p_sin = kruskal(*sin_groups)# 對cos分量做Kruskal-Wallis檢驗
cos_groups = [np.cos(g) for g in hue_rad_groups]
stat_cos, p_cos = kruskal(*cos_groups)print(f"Kruskal-Wallis檢驗(sin分量)p值: {p_sin:.4g}")
print(f"Kruskal-Wallis檢驗(cos分量)p值: {p_cos:.4g}")if p_sin < 0.05 or p_cos < 0.05:print("不同成熟度組的色相分布存在顯著差異。")
else:print("不同成熟度組的色相分布無顯著差異。")
Kruskal-Wallis檢驗(sin分量)p值: 7.903e-44
Kruskal-Wallis檢驗(cos分量)p值: 3.366e-34
不同成熟度組的色相分布存在顯著差異。
6.3卡方檢驗
對于最后的顏色特征,這里采用的是卡方檢驗,卡方檢驗是一種用于分類變量之間關(guān)系分析的統(tǒng)計方法,主要用于判斷兩個(或多個)分類變量是否獨立無關(guān)。
contingency_table = pd.crosstab(data['color_category'], data['ripeness'])
print(contingency_table)
ripeness breaking firm-ripe hard pre-conditioned ripe
color_category
black 0 25 0 0 50
dark green 0 0 50 0 0
green 0 0 0 50 0
purple 50 25 0 0 0
chi2, p, dof, expected = chi2_contingency(contingency_table)
print(f"Chi2統(tǒng)計量: {chi2:.3f}")
print(f"p值: {p:.4f}")
print(f"自由度: {dof}")
print("理論頻數(shù)表:\n", pd.DataFrame(expected, index=contingency_table.index, columns=contingency_table.columns))
Chi2統(tǒng)計量: 666.667
p值: 0.0000
自由度: 12
理論頻數(shù)表:ripeness breaking firm-ripe hard pre-conditioned ripe
color_category
black 15.0 15.0 15.0 15.0 15.0
dark green 10.0 10.0 10.0 10.0 10.0
green 10.0 10.0 10.0 10.0 10.0
purple 15.0 15.0 15.0 15.0 15.0
理論頻數(shù)表(expected counts table)是指:
在卡方檢驗中,假設(shè)兩個變量(如color_category和ripeness)完全獨立,那么每個單元格“理論上”應(yīng)該出現(xiàn)多少個樣本。
計算方法
對于某一單元格(第i行第j列):
E i j = ( 第i行總和 ) × ( 第j列總和 ) 總樣本數(shù) E_{ij} = \frac{(\text{第i行總和}) \times (\text{第j列總和})}{\text{總樣本數(shù)}} Eij?=總樣本數(shù)(第i行總和)×(第j列總和)?
- 例如,如果某行總和是100,某列總和是50,總樣本數(shù)是200,那么該單元格的理論頻數(shù)就是 100 × 50 200 = 25 \frac{100 \times 50}{200} = 25 200100×50?=25。
作用
- 卡方檢驗就是比較“實際觀測頻數(shù)”(表里的數(shù)字)和“理論頻數(shù)”之間的差異。
- 如果差異很大,說明變量之間有關(guān)聯(lián);如果差異很小,說明變量獨立。
7.預(yù)測牛油果成熟度
7.1數(shù)據(jù)預(yù)處理
data['hue_sin'] = np.sin(data['hue_rad'])
data['hue_cos'] = np.cos(data['hue_rad'])
features = ['firmness','hue_sin','hue_cos', 'saturation', 'brightness', 'sound_db', 'weight_g', 'size_cm3', 'color_category']
target = 'ripeness'
new_data = data[features + [target]].copy()
new_data = pd.get_dummies(new_data, columns=['color_category'], prefix='color', dtype=int)
x = new_data.drop(columns=[target])
y = new_data[target]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=15, stratify=y)
scaler = StandardScaler()
num_cols = ['firmness', 'saturation', 'brightness', 'sound_db', 'weight_g', 'size_cm3']
x_train[num_cols] = scaler.fit_transform(x_train[num_cols])
x_test[num_cols] = scaler.transform(x_test[num_cols])
這里補充兩個點:
- 數(shù)據(jù)預(yù)處理的時候,我之前總是喜歡先標準化后再進行數(shù)據(jù)劃分,這樣實際上有數(shù)據(jù)泄露的風險。
stratify=y
的作用是分層抽樣,即保證訓(xùn)練集和測試集中的各類別比例與原始數(shù)據(jù)一致。
7.2 多分類邏輯回歸
# solver='lbfgs' 支持多分類,multi_class='multinomial' 表示用softmax
log_clf = LogisticRegression(multi_class='multinomial', solver='lbfgs', max_iter=1000, random_state=15)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=15)
log_cv_scores = cross_val_score(log_clf, x_train, y_train, cv=cv, scoring='accuracy')
print("邏輯回歸 5折交叉驗證準確率:", log_cv_scores)
print("邏輯回歸 平均交叉驗證準確率:", log_cv_scores.mean())
邏輯回歸 5折交叉驗證準確率: [1. 0.925 0.975 1. 1. ]
邏輯回歸 平均交叉驗證準確率: 0.9800000000000001
- 每一折的準確率分別為:100%、92.5%、97.5%、100%、100%。 平均準確率為98%。
- 模型在不同訓(xùn)練子集上的表現(xiàn)都非常好,準確率都很高,且波動很小,說明特征對成熟度的預(yù)測能力很強,模型泛化能力也很不錯。
log_clf.fit(x_train, y_train)
log_y_pred = log_clf.predict(x_test)
print("邏輯回歸模型分類報告:\n", classification_report(y_test, log_y_pred))
邏輯回歸模型分類報告:precision recall f1-score supportbreaking 0.91 1.00 0.95 10firm-ripe 1.00 0.90 0.95 10hard 1.00 1.00 1.00 10
pre-conditioned 1.00 1.00 1.00 10ripe 1.00 1.00 1.00 10accuracy 0.98 50macro avg 0.98 0.98 0.98 50weighted avg 0.98 0.98 0.98 50
log_cm = confusion_matrix(y_test, log_y_pred, labels=ripeness_order)
log_disp = ConfusionMatrixDisplay(confusion_matrix=log_cm, display_labels=ripeness_order)
fig, ax = plt.subplots(figsize=(10, 8))
log_disp.plot(ax=ax, cmap='Blues', colorbar=True)
plt.title("圖 4 邏輯回歸多分類混淆矩陣", fontsize=16)
plt.xlabel("預(yù)測標簽")
plt.ylabel("真實標簽")
plt.show()

- 準確率 (accuracy):0.98,即50個樣本中有49個被正確分類。
- macro avg / weighted avg:都為0.98,說明各類別表現(xiàn)均衡,沒有某一類被“犧牲”。
- 根據(jù)混淆矩陣發(fā)現(xiàn),絕大多數(shù)樣本都被正確分類(對角線上的數(shù)字為10,只有一個“硬熟”被誤分為“轉(zhuǎn)熟”)。
7.3支持向量機
# 建立SVM模型(多分類,RBF核)
svm_clf = SVC(kernel='rbf', decision_function_shape='ovr', probability=True, random_state=15)
svm_cv_scores = cross_val_score(svm_clf, x_train, y_train, cv=cv, scoring='accuracy')
print("SVM 5折交叉驗證準確率:", svm_cv_scores)
print("SVM 平均交叉驗證準確率:", svm_cv_scores.mean())
SVM 5折交叉驗證準確率: [1. 0.975 0.975 1. 1. ]
SVM 平均交叉驗證準確率: 0.99
- 每一折的準確率分別為:100%、97.5%、97.5%、100%、100%。 平均準確率為99%。
- 模型在不同訓(xùn)練子集上的表現(xiàn)都非常好,準確率都很高,且波動很小,說明特征對成熟度的預(yù)測能力很強,模型泛化能力也很不錯。
svm_clf.fit(x_train, y_train)
svm_y_pred = svm_clf.predict(x_test)
print("SVM 分類報告:\n", classification_report(y_test, svm_y_pred))
SVM 分類報告:precision recall f1-score supportbreaking 1.00 1.00 1.00 10firm-ripe 1.00 1.00 1.00 10hard 1.00 1.00 1.00 10
pre-conditioned 1.00 1.00 1.00 10ripe 1.00 1.00 1.00 10accuracy 1.00 50macro avg 1.00 1.00 1.00 50weighted avg 1.00 1.00 1.00 50
svm_cm = confusion_matrix(y_test, svm_y_pred, labels=ripeness_order)
svm_disp = ConfusionMatrixDisplay(confusion_matrix=svm_cm, display_labels=ripeness_order)
fig, ax = plt.subplots(figsize=(10, 8))
svm_disp.plot(ax=ax, cmap='Blues', colorbar=True)
plt.title("圖 5 SVM 多分類混淆矩陣", fontsize=16)
plt.xlabel("預(yù)測標簽")
plt.ylabel("真實標簽")
plt.show()

- 每個類別的精確率、召回率、F1分數(shù)都是1.00。
- 整體準確率100%,macro avg和weighted avg也都是1.00。
- 根據(jù)混淆矩陣發(fā)現(xiàn)對角線全為10,其他全為0,說明每個類別的10個樣本全部被正確分類,沒有任何誤分。
7.4隨機森林
rf_clf = RandomForestClassifier(n_estimators=100, random_state=15)
rf_cv_scores = cross_val_score(rf_clf, x_train, y_train, cv=cv, scoring='accuracy')
print("隨機森林 5折交叉驗證準確率:", rf_cv_scores)
print("隨機森林 平均交叉驗證準確率:", rf_cv_scores.mean())
隨機森林 5折交叉驗證準確率: [1. 1. 1. 1. 1.]
隨機森林 平均交叉驗證準確率: 1.0
- 每一折的準確率都是100%!
- 模型在不同訓(xùn)練子集上的表現(xiàn)可以用夸張來形容。
rf_clf.fit(x_train, y_train)
rf_y_pred = rf_clf.predict(x_test)
print("\n隨機森林 分類報告:\n", classification_report(y_test, rf_y_pred))
隨機森林 分類報告:precision recall f1-score supportbreaking 1.00 1.00 1.00 10firm-ripe 1.00 1.00 1.00 10hard 1.00 1.00 1.00 10
pre-conditioned 1.00 1.00 1.00 10ripe 1.00 1.00 1.00 10accuracy 1.00 50macro avg 1.00 1.00 1.00 50weighted avg 1.00 1.00 1.00 50
rf_cm = confusion_matrix(y_test, rf_y_pred, labels=ripeness_order)
rf_disp = ConfusionMatrixDisplay(confusion_matrix=rf_cm, display_labels=ripeness_order)
fig, ax = plt.subplots(figsize=(10, 8))
rf_disp.plot(ax=ax, cmap='Blues', colorbar=True)
plt.title("圖 6 隨機森林多分類混淆矩陣", fontsize=16)
plt.xlabel("預(yù)測標簽")
plt.ylabel("真實標簽")
plt.show()

- 每個類別的精確率、召回率、F1分數(shù)都是1.00。
- 整體準確率100%,macro avg和weighted avg也都是1.00。
- 根據(jù)混淆矩陣發(fā)現(xiàn)對角線全為10,其他全為0,說明每個類別的10個樣本全部被正確分類,沒有任何誤分。
8.總結(jié)
本項目基于對牛油果合成數(shù)據(jù)的系統(tǒng)分析,得出以下主要結(jié)論:
- 數(shù)據(jù)特征:果實硬度平均為50.6 N,主要分布在25.8-74.1 N區(qū)間;色相平均125.9°,分布不均且四分位差較大(25.3-278.8°);飽和度平均64.4%,大部分集中在51.3-76.8%;亮度平均45.1%,分布范圍為10-78%;敲擊聲分貝平均58.0 dB,主要分布在47.3-68.0 dB;重量平均220.2 g,四分位范圍為193.3-245.0 g;體積平均208.6 cm3,分布范圍較廣(100-299 cm3)。分類特征方面,顏色類別分為black(75個)、dark green(50個)、green(50個)、purple(75個);成熟度級別共5類,每類各50個樣本。同時發(fā)現(xiàn)存在如“質(zhì)量越大體積越小”不合理現(xiàn)象,反映了數(shù)據(jù)生成時的不足。
- 統(tǒng)計檢驗:通過對數(shù)據(jù)進行Mann-Whitney U檢驗、Kruskal-Wallis 檢驗、卡方檢驗,發(fā)現(xiàn)果實硬度、果皮色相、色彩飽和度、亮度、顏色、敲擊聲分貝、重量、體積均對果實成熟度的識別有影響。
- 機器學(xué)習(xí):構(gòu)建了邏輯回歸模型、支持向量機、隨機森林三個模型,預(yù)測效果都非常好,其中支持向量機和隨機森林甚至達到了100%的準確率,也佐證了這個合成數(shù)據(jù)的特征差異非常明顯。