Spaces:
Running
Running
File size: 4,499 Bytes
8ff1b66 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """
Moduł do obliczania statystycznej wielkości próbki.
Implementuje wzory statystyczne dla próbkowania populacji.
"""
import math
from dataclasses import dataclass
from app.core.config import settings
# Wartości Z dla poziomów ufności
Z_SCORES = {
0.90: 1.645,
0.95: 1.96,
0.99: 2.576,
}
@dataclass
class SamplePlan:
"""
Plan próbkowania dla gry.
Attributes:
top_helpful: Liczba najprzydatniejszych recenzji.
statistical_sample: Wielkość próbki statystycznej.
positive_count: Ile pobrać pozytywnych (stratified).
negative_count: Ile pobrać negatywnych (stratified).
total: Łączna liczba recenzji do pobrania.
"""
top_helpful: int
statistical_sample: int
positive_count: int
negative_count: int
total: int
def calculate_sample_size(
population: int,
confidence_level: float | None = None,
margin_of_error: float | None = None,
) -> int:
"""
Oblicza minimalną wielkość próbki dla danej populacji.
Wykorzystuje wzór Cochrana z korektą dla populacji skończonej.
"""
if confidence_level is None:
confidence_level = settings.sample_confidence_level
if margin_of_error is None:
margin_of_error = settings.sample_margin_of_error
# 1. Pobieramy Z-score (np. 1.96 dla 95% ufności).
# Mówi on, jak bardzo wynik może odbiegać od średniej w jednostkach odchylenia standardowego.
z = Z_SCORES.get(confidence_level, 1.96)
# 2. Zakładamy p=0.5 (maksymalna zmienność).
# To daje nam najbezpieczniejszą (największą) wielkość próbki.
p = 0.5
# 3. Wzór Cochrana dla nieskończonej populacji:
# n0 = (Z^2 * p * (1-p)) / e^2
# Wyjaśnienie: Z kwadrat razy zmienność, podzielone przez kwadrat błędu.
n_0 = (z ** 2 * p * (1 - p)) / (margin_of_error ** 2)
# 4. Korekta dla populacji skończonej (Steam ma policzalną liczbę recenzji):
# n = n0 / (1 + (n0 - 1) / N)
# Wyjaśnienie: Zmniejszamy próbkę, bo wiemy dokładnie, ile osób (recenzji) jest w "całym świecie" tej gry.
n = n_0 / (1 + (n_0 - 1) / population)
# Zaokrąglamy w górę do pełnej recenzji
return math.ceil(n)
def create_sample_plan(
total_reviews: int,
positive_reviews: int,
negative_reviews: int,
) -> SamplePlan:
"""
Tworzy plan próbkowania, łącząc dwa podejścia.
"""
top_helpful = settings.sample_top_helpful
max_reviews = settings.sample_max_reviews
# Obliczamy, ile recenzji musimy pobrać, żeby wynik był wiarygodny
statistical_sample = calculate_sample_size(total_reviews)
# Pilnujemy, żeby nie przekroczyć ustawionego limitu (np. 3000)
statistical_sample = min(statistical_sample, max_reviews - top_helpful)
# Obliczamy jaki procent stanowią pozytywy i negatywy w całości
if total_reviews > 0:
pos_ratio = positive_reviews / total_reviews
neg_ratio = negative_reviews / total_reviews
else:
pos_ratio = 0.5
neg_ratio = 0.5
# Rozdzielamy naszą próbkę proporcjonalnie do tych wyników (Stratified Sampling)
pos_target = math.ceil(statistical_sample * pos_ratio)
neg_target = math.ceil(statistical_sample * neg_ratio)
# Minority protection: boost the smaller group to minority_min if possible
minority_min = settings.sample_minority_min
if pos_target < minority_min and positive_reviews > pos_target:
pos_target = min(minority_min, positive_reviews)
if neg_target < minority_min and negative_reviews > neg_target:
neg_target = min(minority_min, negative_reviews)
# Final adjustment to stay within statistical_sample limit
if pos_target + neg_target > statistical_sample:
if pos_target > neg_target:
pos_target = max(pos_target - (pos_target + neg_target - statistical_sample), minority_min)
else:
neg_target = max(neg_target - (pos_target + neg_target - statistical_sample), minority_min)
# Final cap by actual availability
positive_count = min(pos_target, positive_reviews)
negative_count = min(neg_target, negative_reviews)
# Sumujemy wszystko (Top Helpful + Próbka Statystyczna)
total = top_helpful + positive_count + negative_count
return SamplePlan(
top_helpful=top_helpful,
statistical_sample=statistical_sample,
positive_count=positive_count,
negative_count=negative_count,
total=total,
)
|