Automatyczna analiza statystyczna dla protokołu 10⁴ prób (RNG)
Aneks do artykułu:
Psychokineza na mikroskalę: wpływ intencji na fluktuacje generatora liczb losowych – zaawansowane badania psychotroniczne
Przeznaczenie
Skrypt przeznaczony jest dla profesjonalistów, którzy zebrali dane z eksperymentu micro-PK (co najmniej 10 000 prób z podziałem na warunki HIGH, LOW, BASELINE) i chcą szybko, rzetelnie i zgodnie z wymogami metodologicznymi (korekta Bonferroniego) ocenić, czy uzyskane odchylenia są statystycznie istotne.
Wymagania
- Python 3.8+
- Biblioteki:
pandas,numpy,scipy,matplotlib(do opcjonalnego wykresu) - Plik wejściowy: CSV z kolumnami:
trial(numer próby),intention(HIGH/LOW/BASELINE),score(wynik 0–200)
Kod źródłowy
python
"""
Micro-PK Data Analyzer
Analiza statystyczna dla eksperymentów z generatorem zdarzeń losowych (RNG).
Wykonuje test chi² dobroci dopasowania (dla rozkładu) oraz test t-Studenta dla średnich,
z automatyczną korektą Bonferroniego (α = 0.05/3 = 0.0167).
"""
import pandas as pd
import numpy as np
from scipy.stats import chisquare, ttest_1samp
from statsmodels.stats.multitest import multipletests
import argparse
def load_data(filepath):
"""Wczytuje dane z pliku CSV."""
df = pd.read_csv(filepath)
required_cols = {'trial', 'intention', 'score'}
if not required_cols.issubset(df.columns):
raise ValueError(f"Plik musi zawierać kolumny: {required_cols}")
return df
def expected_distribution_for_200bits():
"""
Zwraca teoretyczny rozkład liczby sukcesów dla 200 bitów przy p=0.5 (dwumianowy).
Używamy przybliżenia normalnego z poprawką na ciągłość,
ale dla uproszczenia – oczekiwana liczba prób w każdym przedziale.
Tutaj dla testu chi² grupujemy wyniki w przedziały o szerokości 10.
"""
# Dla każdego możliwego wyniku 0-200, prawdopodobieństwo z rozkładu dwumianowego
n = 200
p = 0.5
probs = [np.math.comb(n, k) * (p**k) * ((1-p)**(n-k)) for k in range(n+1)]
return probs
def run_chi_square_grouped(scores, bins=20):
"""
Test chi² dobroci dopasowania dla zgrupowanych wyników.
scores: lista wyników (0-200)
bins: liczba przedziałów (domyślnie 20, czyli przedziały co 10)
"""
observed, bin_edges = np.histogram(scores, bins=bins, range=(0,200))
# Oczekiwane prawdopodobieństwo dla każdego przedziału przy rozkładzie dwumianowym
n = 200
probs_expected = []
for i in range(len(bin_edges)-1):
low = bin_edges[i]
high = bin_edges[i+1]
# Prawdopodobieństwo, że wynik należy do [low, high)
if i == len(bin_edges)-2:
prob = sum(np.math.comb(n, k) * (0.5**n) for k in range(int(low), int(high)+1))
else:
prob = sum(np.math.comb(n, k) * (0.5**n) for k in range(int(low), int(high)))
probs_expected.append(prob)
expected = np.array(probs_expected) * len(scores)
# Usuwamy przedziały z zerową oczekiwaną liczbą
mask = expected > 0
chi2, p = chisquare(observed[mask], f_exp=expected[mask])
return chi2, p
def analyze(df, alpha=0.05):
"""Główna funkcja analizy."""
intentions = ['HIGH', 'LOW', 'BASELINE']
results = []
p_values = []
for intent in intentions:
subset = df[df['intention'].str.upper() == intent]['score']
if len(subset) == 0:
results.append({'intention': intent, 'n': 0, 'mean': np.nan, 'chi2_p': np.nan, 't_p': np.nan})
p_values.append(np.nan)
continue
mean = subset.mean()
# Test t-Studenta dla średniej względem wartości oczekiwanej 100
t_stat, t_p = ttest_1samp(subset, 100)
# Test chi² dla rozkładu
try:
chi2, chi_p = run_chi_square_grouped(subset)
except Exception as e:
chi_p = np.nan
results.append({
'intention': intent,
'n': len(subset),
'mean': mean,
't_stat': t_stat,
't_p': t_p,
'chi2_p': chi_p
})
p_values.append(t_p) # do korekty Bonferroniego używamy p z testu t
# Korekta Bonferroniego (dla 3 porównań)
reject_bonf, p_corrected, _, _ = multipletests(p_values, alpha=alpha, method='bonferroni', is_sorted=False, returnsorted=False)
# Prezentacja wyników
print("\n" + "="*70)
print("WYNIKI ANALIZY MICRO-PK (10⁴ prób)")
print("="*70)
print(f"Poziom istotności α = {alpha}, korekta Bonferroniego → α_adj = {alpha/3:.4f}")
print("-"*70)
print(f"{'Intencja':<12} {'N':>8} {'Średnia':>10} {'t-stat':>10} {'p (t-test)':>12} {'Chi² p':>12} {'Istotne?':>10}")
print("-"*70)
for i, res in enumerate(results):
if pd.isna(res['t_p']):
significant = "BRAK DANYCH"
else:
significant = "TAK (p<0.0167)" if (reject_bonf[i] and res['t_p'] < alpha/3) else "NIE"
print(f"{res['intention']:<12} {res['n']:>8} {res['mean']:>10.3f} {res.get('t_stat', 0):>10.3f} {res['t_p']:>12.6f} {res['chi2_p']:>12.6f} {significant:>10}")
print("-"*70)
# Dodatkowe wnioski
print("\nWNIOSKI:")
high_sig = (reject_bonf[0] and results[0]['t_p'] < alpha/3 and results[0]['mean'] > 100)
low_sig = (reject_bonf[1] and results[1]['t_p'] < alpha/3 and results[1]['mean'] < 100)
base_sig = (reject_bonf[2] and results[2]['t_p'] < alpha/3)
if high_sig:
print("✓ WARUNEK HIGH: Zaobserwowano statystycznie istotne odchylenie ŚREDNIEJ w górę (p < 0.0167). Wskazuje to na zdolność do micro-PK w kierunku intencji.")
elif results[0]['t_p'] < alpha/3 and results[0]['mean'] > 100:
print("! WARUNEK HIGH: Istotne statystycznie (p < 0.0167), ale po korekcie Bonferroniego – nie. Efekt może istnieć, ale jest słaby lub potrzebna większa próba.")
else:
print("✗ WARUNEK HIGH: Brak istotnego odchylenia średniej. Nie potwierdzono micro-PK w tym kierunku.")
if low_sig:
print("✓ WARUNEK LOW: Zaobserwowano statystycznie istotne odchylenie ŚREDNIEJ w dół (p < 0.0167). Wskazuje to na zdolność do micro-PK w kierunku intencji.")
elif results[1]['t_p'] < alpha/3 and results[1]['mean'] < 100:
print("! WARUNEK LOW: Istotne statystycznie (p < 0.0167), ale po korekcie Bonferroniego – nie. Efekt może istnieć.")
else:
print("✗ WARUNEK LOW: Brak istotnego odchylenia średniej.")
if base_sig:
print("⚠ UWAGA: WARUNEK BASELINE wykazał istotne odchylenie. Oznacza to możliwy bias RNG lub niekontrolowany efekt eksperymentatora. Sprawdź kalibrację urządzenia.")
else:
print("✓ WARUNEK BASELINE: Brak istotnego odchylenia – RNG działa zgodnie z oczekiwaniami.")
return results
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Analiza danych micro-PK z RNG')
parser.add_argument('file', type=str, help='Ścieżka do pliku CSV z danymi')
parser.add_argument('--alpha', type=float, default=0.05, help='Poziom istotności (domyślnie 0.05)')
args = parser.parse_args()
df = load_data(args.file)
analyze(df, alpha=args.alpha)Instrukcja uruchomienia
- Przygotuj dane w pliku CSV o nazwie np.
moj_eksperyment.csvo strukturze:
csv
trial,intention,score 1,HIGH,101 2,LOW,98 3,BASELINE,100 4,HIGH,103 ...
- Zapisz powyższy kod w pliku
micro_pk_analyzer.py. - Uruchom z terminala:
bash
python micro_pk_analyzer.py moj_eksperyment.csv
Lub z własnym poziomem istotności:
bash
python micro_pk_analyzer.py moj_eksperyment.csv --alpha 0.05
Przykładowy wydruk
text
====================================================================== WYNIKI ANALIZY MICRO-PK (10⁴ prób) ====================================================================== Poziom istotności α = 0.05, korekta Bonferroniego → α_adj = 0.0167 ---------------------------------------------------------------------- Intencja N Średnia t-stat p (t-test) Chi² p Istotne? ---------------------------------------------------------------------- HIGH 3333 100.234 2.451 0.014328 0.3210 TAK (p<0.0167) LOW 3334 99.823 -1.892 0.058621 0.4521 NIE BASELINE 3333 100.011 0.342 0.732400 0.8912 NIE ---------------------------------------------------------------------- WNIOSKI: ✓ WARUNEK HIGH: Zaobserwowano statystycznie istotne odchylenie ŚREDNIEJ w górę (p < 0.0167). Wskazuje to na zdolność do micro-PK w kierunku intencji. ✗ WARUNEK LOW: Brak istotnego odchylenia średniej. ✓ WARUNEK BASELINE: Brak istotnego odchylenia – RNG działa zgodnie z oczekiwaniami.
Uwagi końcowe
- Test chi² dla rozkładu jest bardziej wymagający obliczeniowo i czuły na nietypowe kształty rozkładu. W praktyce micro-PK, test t-Studenta dla średnich jest częściej stosowany i wystarczający, ponieważ hipoteza dotyczy przesunięcia wartości oczekiwanej.
- Korekta Bonferroniego jest konserwatywna – w razie wątpliwości można zastosować metodę FDR (Benjamini-Hochberga), zmieniając w kodzie
method='fdr_bh'. - Dla uzyskania pełnej wiarygodności, przed analizą upewnij się, że nie naruszono założeń ślepej próby i że dane nie były selektywnie odrzucane.
Czy chciałbyś, abym przygotował również szablon pliku CSV generatora losowego (symulację do testowania kodu) lub dodał funkcję generowania wykresu wyników?
zrób szablon i dodaj funkcję generowania
Oczywiście. Poniżej znajduje się kompletny, rozszerzony skrypt Python wraz z:
- Funkcją generowania przykładowego pliku CSV (z symulowanymi danymi z efektem micro-PK lub bez).
- Funkcją rysowania profesjonalnych wykresów (histogramy dla każdego warunku, porównanie średnich z przedziałami ufności).
- Zunifikowanym interfejsem (argumenty
--generate,--plot,--file).
Dodatkowo podaję szablon CSV w postaci czytelnej dla użytkownika.
1. Szablon pliku CSV (do ręcznego wypełnienia)
Zapisz poniższe nagłówki jako moj_eksperyment.csv i wypełnij danymi. Każdy wiersz to jedna próba (200 bitów, wynik 0–200).
csv
trial,intention,score 1,HIGH,101 2,LOW,98 3,BASELINE,100 4,HIGH,103 5,LOW,97 6,BASELINE,99 ...
Legenda:
trial– numer próby (kolejny, unikalny)intention– warunek:HIGH,LOWlubBASELINEscore– liczba uzyskanych „jedynek” w próbie (zakres 0–200)
2. Pełny kod Pythona z funkcjami generowania i wykresu
python
"""
Micro-PK Data Analyzer – pełna wersja z generowaniem danych i wykresami
Autor: Zespół Ekspertów Psychotroniki
Licencja: do użytku profesjonalnego i badawczego
"""
import pandas as pd
import numpy as np
from scipy.stats import chisquare, ttest_1samp
from statsmodels.stats.multitest import multipletests
import matplotlib.pyplot as plt
import seaborn as sns
import argparse
import os
# ============================================================
# 1. GENEROWANIE PRZYKŁADOWYCH DANYCH (symulacja RNG)
# ============================================================
def generate_sample_data(filepath, n_trials=10000, effect_strength=0.0, seed=42):
"""
Generuje symulowane dane RNG.
effect_strength: odchylenie od wartości oczekiwanej 100 dla HIGH (dodatnie) i LOW (ujemne).
Jeśli effect_strength = 0.5, to HIGH będzie miało średnią ~100.5, LOW ~99.5.
"""
np.random.seed(seed)
intentions = ['HIGH', 'LOW', 'BASELINE']
# równa liczba prób na warunek
n_per_condition = n_trials // 3
data = []
trial_id = 1
for intent in intentions:
for _ in range(n_per_condition):
if intent == 'HIGH':
mean = 100 + effect_strength
elif intent == 'LOW':
mean = 100 - effect_strength
else:
mean = 100
# symulacja wyniku z rozkładu normalnego (zaokrąglamy do int, odcięcie 0-200)
score = int(np.random.normal(mean, 7.07)) # 7.07 ~ odch. std. dla 200 bitów
score = max(0, min(200, score))
data.append([trial_id, intent, score])
trial_id += 1
df = pd.DataFrame(data, columns=['trial', 'intention', 'score'])
df.to_csv(filepath, index=False)
print(f"Wygenerowano przykładowy plik: {filepath}")
print(f"Liczba prób: {len(df)} (po {n_per_condition} na warunek)")
if effect_strength != 0:
print(f"Wprowadzono sztuczny efekt: HIGH mean = 100+{effect_strength}, LOW mean = 100-{effect_strength}")
return df
# ============================================================
# 2. FUNKCJE ANALITYCZNE (jak poprzednio)
# ============================================================
def load_data(filepath):
df = pd.read_csv(filepath)
required_cols = {'trial', 'intention', 'score'}
if not required_cols.issubset(df.columns):
raise ValueError(f"Plik musi zawierać kolumny: {required_cols}")
return df
def expected_distribution_for_200bits():
n = 200
p = 0.5
probs = [np.math.comb(n, k) * (p**k) * ((1-p)**(n-k)) for k in range(n+1)]
return probs
def run_chi_square_grouped(scores, bins=20):
observed, bin_edges = np.histogram(scores, bins=bins, range=(0,200))
n = 200
probs_expected = []
for i in range(len(bin_edges)-1):
low = bin_edges[i]
high = bin_edges[i+1]
if i == len(bin_edges)-2:
prob = sum(np.math.comb(n, k) * (0.5**n) for k in range(int(low), int(high)+1))
else:
prob = sum(np.math.comb(n, k) * (0.5**n) for k in range(int(low), int(high)))
probs_expected.append(prob)
expected = np.array(probs_expected) * len(scores)
mask = expected > 0
chi2, p = chisquare(observed[mask], f_exp=expected[mask])
return chi2, p
def analyze(df, alpha=0.05):
intentions = ['HIGH', 'LOW', 'BASELINE']
results = []
p_values = []
for intent in intentions:
subset = df[df['intention'].str.upper() == intent]['score']
if len(subset) == 0:
results.append({'intention': intent, 'n': 0, 'mean': np.nan, 't_p': np.nan, 'chi2_p': np.nan})
p_values.append(np.nan)
continue
mean = subset.mean()
t_stat, t_p = ttest_1samp(subset, 100)
try:
chi2, chi_p = run_chi_square_grouped(subset)
except:
chi_p = np.nan
results.append({
'intention': intent,
'n': len(subset),
'mean': mean,
't_stat': t_stat,
't_p': t_p,
'chi2_p': chi_p
})
p_values.append(t_p)
# korekta Bonferroniego
reject_bonf, p_corrected, _, _ = multipletests(p_values, alpha=alpha, method='bonferroni', is_sorted=False)
# wydruk tabeli
print("\n" + "="*80)
print("WYNIKI ANALIZY MICRO-PK")
print("="*80)
print(f"Poziom istotności α = {alpha}, korekta Bonferroniego → α_adj = {alpha/3:.4f}")
print("-"*80)
print(f"{'Intencja':<12} {'N':>8} {'Średnia':>10} {'t-stat':>10} {'p (t-test)':>12} {'Chi² p':>12} {'Istotne?':>12}")
print("-"*80)
for i, res in enumerate(results):
if pd.isna(res['t_p']):
sig = "BRAK DANYCH"
else:
sig = "TAK (p<α_adj)" if (reject_bonf[i] and res['t_p'] < alpha/3) else "NIE"
print(f"{res['intention']:<12} {res['n']:>8} {res['mean']:>10.3f} {res.get('t_stat',0):>10.3f} {res['t_p']:>12.6f} {res['chi2_p']:>12.6f} {sig:>12}")
print("-"*80)
# wnioski
print("\nWNIOSKI:")
high_sig = (reject_bonf[0] and results[0]['t_p'] < alpha/3 and results[0]['mean'] > 100)
low_sig = (reject_bonf[1] and results[1]['t_p'] < alpha/3 and results[1]['mean'] < 100)
base_sig = (reject_bonf[2] and results[2]['t_p'] < alpha/3)
if high_sig:
print("✓ HIGH: Istotne odchylenie w górę (micro-PK potwierdzone).")
else:
print("✗ HIGH: Brak istotnego odchylenia.")
if low_sig:
print("✓ LOW: Istotne odchylenie w dół (micro-PK potwierdzone).")
else:
print("✗ LOW: Brak istotnego odchylenia.")
if base_sig:
print("⚠ BASELINE: Istotne odchylenie – możliwy bias RNG.")
else:
print("✓ BASELINE: Brak odchylenia – RNG działa poprawnie.")
return results
# ============================================================
# 3. GENEROWANIE WYKRESÓW
# ============================================================
def plot_results(df, output_file='micro_pk_plots.png'):
"""
Tworzy profesjonalne wykresy:
- Histogramy wyników dla każdego warunku (z krzywą rozkładu normalnego)
- Wykres słupkowy średnich z 95% przedziałami ufności
"""
sns.set_style("whitegrid")
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
ax_hist_high = axes[0, 0]
ax_hist_low = axes[0, 1]
ax_hist_base = axes[1, 0]
ax_bar = axes[1, 1]
intentions = ['HIGH', 'LOW', 'BASELINE']
colors = {'HIGH': 'green', 'LOW': 'red', 'BASELINE': 'gray'}
means = []
ci_low = []
ci_high = []
for i, intent in enumerate(intentions):
subset = df[df['intention'].str.upper() == intent]['score']
if len(subset) == 0:
continue
# histogram
if intent == 'HIGH':
ax = ax_hist_high
elif intent == 'LOW':
ax = ax_hist_low
else:
ax = ax_hist_base
ax.hist(subset, bins=30, density=True, alpha=0.6, color=colors[intent], edgecolor='black', label=f'{intent} (n={len(subset)})')
# krzywa normalna dopasowana do danych
mu, std = subset.mean(), subset.std()
x = np.linspace(subset.min(), subset.max(), 100)
ax.plot(x, (1/(std*np.sqrt(2*np.pi)))*np.exp(-0.5*((x-mu)/std)**2), color='black', linestyle='--', linewidth=1.5)
ax.set_title(f'Warunek {intent}')
ax.set_xlabel('Wynik (liczba 1 w 200 bitach)')
ax.set_ylabel('Gęstość')
ax.legend()
# dla wykresu słupkowego
mean = subset.mean()
sem = subset.std() / np.sqrt(len(subset))
ci = 1.96 * sem # 95% CI
means.append(mean)
ci_low.append(mean - ci)
ci_high.append(mean + ci)
# Wykres słupkowy średnich z przedziałami ufności
x_pos = np.arange(len(intentions))
ax_bar.bar(x_pos, means, yerr=[mean - ci_low for mean, ci_low in zip(means, ci_low)],
capsize=5, color=[colors[i] for i in intentions], alpha=0.7, edgecolor='black')
ax_bar.axhline(y=100, color='black', linestyle='-', linewidth=1.5, label='Wartość oczekiwana (100)')
ax_bar.set_xticks(x_pos)
ax_bar.set_xticklabels(intentions)
ax_bar.set_ylabel('Średni wynik')
ax_bar.set_title('Średnie wyników z 95% przedziałem ufności')
ax_bar.legend()
plt.suptitle('Analiza wizualna eksperymentu micro-PK', fontsize=16)
plt.tight_layout()
plt.savefig(output_file, dpi=150)
plt.show()
print(f"Wykresy zapisano jako {output_file}")
# ============================================================
# 4. GŁÓWNA FUNKCJA STERUJĄCA
# ============================================================
def main():
parser = argparse.ArgumentParser(description='Analiza danych micro-PK z RNG')
parser.add_argument('--generate', type=str, help='Generuje przykładowy plik CSV (podaj nazwę pliku)')
parser.add_argument('--effect', type=float, default=0.5, help='Siła efektu dla generowanych danych (domyślnie 0.5)')
parser.add_argument('--file', type=str, help='Ścieżka do pliku CSV z danymi do analizy')
parser.add_argument('--plot', action='store_true', help='Generuje wykresy (wymaga --file)')
parser.add_argument('--alpha', type=float, default=0.05, help='Poziom istotności (domyślnie 0.05)')
args = parser.parse_args()
if args.generate:
generate_sample_data(args.generate, n_trials=10000, effect_strength=args.effect)
return
if args.file:
df = load_data(args.file)
results = analyze(df, alpha=args.alpha)
if args.plot:
plot_results(df)
return
parser.print_help()
if __name__ == "__main__":
main()3. Jak używać?
A) Wygenerowanie przykładowego pliku CSV (do testowania)
bash
python micro_pk_analyzer.py --generate moj_dane.csv --effect 0.3
--effect 0.3wprowadza sztuczne odchylenie: średnia HIGH = 100.3, LOW = 99.7.- Jeśli pominiesz
--effect, domyślnie efekt = 0.5. - Jeśli chcesz danych losowych bez efektu (null), użyj
--effect 0.
B) Analiza własnego pliku CSV z danymi
bash
python micro_pk_analyzer.py --file moj_eksperyment.csv --plot
--plotgeneruje wykresy (zapisuje jakomicro_pk_plots.pngi wyświetla na ekranie).- Możesz pominąć
--plot, aby otrzymać tylko tabelę i wnioski.
C) Pełna analiza z poziomem istotności
bash
python micro_pk_analyzer.py --file moj_eksperyment.csv --alpha 0.01 --plot
4. Przykładowy wydruk z wykresami (opis)
Po uruchomieniu z --plot pojawią się:
- Histogramy dla HIGH, LOW, BASELINE – każdy z dopasowaną krzywą normalną. Pozwalają ocenić, czy rozkład wyników odbiega od oczekiwanego (symetryczny, dzwonowy).
- Wykres słupkowy średnich z 95% przedziałami ufności. Jeśli przedział dla HIGH nie zawiera 100 i leży powyżej, a dla LOW poniżej – to wizualne potwierdzenie efektu.
Wskazówka: W przypadku silnego efektu micro-PK, słupki HIGH i LOW będą wyraźnie oddalone od linii 100, a przedziały ufności nie będą się z nią przecinać.
5. Szablon CSV do pobrania (treść do skopiowania)
Jeśli wolisz wypełnić ręcznie, ogotuj plik z nagłówkami i danymi. Poniżej minimalny przykład (10 wierszy):
csv
trial,intention,score 1,HIGH,102 2,LOW,97 3,BASELINE,100 4,HIGH,105 5,LOW,94 6,BASELINE,101 7,HIGH,99 8,LOW,96 9,BASELINE,100 10,HIGH,104
Dla rzetelnej analizy potrzebujesz co najmniej 10 000 prób (około 3333 na warunek).
6. Instalacja wymaganych bibliotek
Przed pierwszym uruchomieniem zainstaluj:
bash
pip install pandas numpy scipy matplotlib seaborn statsmodels
Czy chciałbyś, abym dodał również możliwość eksportu wyników do pliku tekstowego (JSON lub CSV) oraz automatycznej walidacji, czy liczba prób na warunek jest wystarczająca?
dodaj mozliwośc eksportu oraz walidację
Rozumiem. Mam rozszerzyć skrypt Pythona o dwie funkcjonalności:
- Eksport wyników analizy do pliku (JSON, CSV lub obu) – aby można było zapisać tabelę z wynikami, wnioski i parametry analizy.
- Walidację danych wejściowych – sprawdzenie, czy liczba prób na warunek jest wystarczająca (np. co najmniej 1000 na warunek, a najlepiej 3333+), czy nie brakuje wartości, czy wyniki mieszczą się w zakresie 0–200 itp.
Dodam te funkcje do istniejącego kodu i przedstawię kompletny, gotowy do użycia skrypt. Dodatkowo zaktualizuję interfejs argumentów (--export i opcję --min_trials_per_condition).
Oto pełny kod z dodanym eksportem i walidacją:
python
"""
Micro-PK Data Analyzer – pełna wersja z generowaniem danych, walidacją, wykresami i eksportem
Autor: Zespół Ekspertów Psychotroniki
Licencja: do użytku profesjonalnego i badawczego
"""
import pandas as pd
import numpy as np
from scipy.stats import chisquare, ttest_1samp
from statsmodels.stats.multitest import multipletests
import matplotlib.pyplot as plt
import seaborn as sns
import argparse
import os
import json
from datetime import datetime
# ============================================================
# 1. GENEROWANIE PRZYKŁADOWYCH DANYCH (symulacja RNG)
# ============================================================
def generate_sample_data(filepath, n_trials=10000, effect_strength=0.0, seed=42):
"""
Generuje symulowane dane RNG.
effect_strength: odchylenie od wartości oczekiwanej 100 dla HIGH (dodatnie) i LOW (ujemne).
"""
np.random.seed(seed)
intentions = ['HIGH', 'LOW', 'BASELINE']
n_per_condition = n_trials // 3
data = []
trial_id = 1
for intent in intentions:
for _ in range(n_per_condition):
if intent == 'HIGH':
mean = 100 + effect_strength
elif intent == 'LOW':
mean = 100 - effect_strength
else:
mean = 100
score = int(np.random.normal(mean, 7.07))
score = max(0, min(200, score))
data.append([trial_id, intent, score])
trial_id += 1
df = pd.DataFrame(data, columns=['trial', 'intention', 'score'])
df.to_csv(filepath, index=False)
print(f"Wygenerowano przykładowy plik: {filepath}")
print(f"Liczba prób: {len(df)} (po {n_per_condition} na warunek)")
if effect_strength != 0:
print(f"Wprowadzono sztuczny efekt: HIGH mean = 100+{effect_strength}, LOW mean = 100-{effect_strength}")
return df
# ============================================================
# 2. WALIDACJA DANYCH
# ============================================================
def validate_data(df, min_trials_per_condition=1000):
"""
Przeprowadza walidację pliku danych:
- sprawdza, czy kolumny istnieją
- sprawdza, czy wartości score mieszczą się w 0-200
- sprawdza, czy nie brakuje wartości
- sprawdza, czy liczba prób na każdy warunek jest >= min_trials_per_condition
- wypisuje ostrzeżenia lub błędy
Zwraca (is_valid, warnings_errors_list)
"""
issues = []
# Sprawdzenie czy kolumny istnieją
required = {'trial', 'intention', 'score'}
if not required.issubset(df.columns):
issues.append(f"Brak wymaganych kolumn: {required - set(df.columns)}")
return False, issues
# Sprawdzenie typu i zakresu score
if not pd.api.types.is_numeric_dtype(df['score']):
issues.append("Kolumna 'score' nie jest numeryczna")
else:
if (df['score'] < 0).any() or (df['score'] > 200).any():
issues.append("Wartości 'score' poza zakresem 0-200")
# Brakujące wartości
if df[['trial', 'intention', 'score']].isnull().any().any():
issues.append("Występują brakujące wartości (NaN)")
# Sprawdzenie poprawności intencji
allowed_intentions = ['HIGH', 'LOW', 'BASELINE']
if not df['intention'].str.upper().isin(allowed_intentions).all():
issues.append("Niepoprawne wartości w kolumnie 'intention' (dozwolone: HIGH, LOW, BASELINE)")
# Liczba prób na warunek
counts = df['intention'].str.upper().value_counts()
for intent in allowed_intentions:
if intent not in counts:
issues.append(f"Brak warunku '{intent}' w danych")
elif counts[intent] < min_trials_per_condition:
issues.append(f"Warunek '{intent}' ma tylko {counts[intent]} prób, a wymagane minimum to {min_trials_per_condition}")
# Duplikaty trial?
if df['trial'].duplicated().any():
issues.append("Wartości w kolumnie 'trial' nie są unikalne")
is_valid = len(issues) == 0
return is_valid, issues
# ============================================================
# 3. FUNKCJE ANALITYCZNE
# ============================================================
def run_chi_square_grouped(scores, bins=20):
observed, bin_edges = np.histogram(scores, bins=bins, range=(0,200))
n = 200
probs_expected = []
for i in range(len(bin_edges)-1):
low = bin_edges[i]
high = bin_edges[i+1]
if i == len(bin_edges)-2:
prob = sum(np.math.comb(n, k) * (0.5**n) for k in range(int(low), int(high)+1))
else:
prob = sum(np.math.comb(n, k) * (0.5**n) for k in range(int(low), int(high)))
probs_expected.append(prob)
expected = np.array(probs_expected) * len(scores)
mask = expected > 0
chi2, p = chisquare(observed[mask], f_exp=expected[mask])
return chi2, p
def analyze(df, alpha=0.05):
intentions = ['HIGH', 'LOW', 'BASELINE']
results = []
p_values = []
for intent in intentions:
subset = df[df['intention'].str.upper() == intent]['score']
if len(subset) == 0:
results.append({'intention': intent, 'n': 0, 'mean': np.nan, 't_p': np.nan, 'chi2_p': np.nan})
p_values.append(np.nan)
continue
mean = subset.mean()
t_stat, t_p = ttest_1samp(subset, 100)
try:
chi2, chi_p = run_chi_square_grouped(subset)
except:
chi_p = np.nan
results.append({
'intention': intent,
'n': len(subset),
'mean': mean,
't_stat': t_stat,
't_p': t_p,
'chi2_p': chi_p
})
p_values.append(t_p)
# korekta Bonferroniego
reject_bonf, p_corrected, _, _ = multipletests(p_values, alpha=alpha, method='bonferroni', is_sorted=False)
# przygotowanie wyników dla raportu
summary = []
for i, res in enumerate(results):
is_sig = (reject_bonf[i] and res['t_p'] < alpha/3) if not pd.isna(res['t_p']) else False
summary.append({
'intention': res['intention'],
'n': res['n'],
'mean': res['mean'],
't_stat': res.get('t_stat', 0),
't_p': res['t_p'],
'chi2_p': res['chi2_p'],
'significant_bonferroni': is_sig
})
# Dodatkowe wnioski tekstowe
high_sig = (reject_bonf[0] and results[0]['t_p'] < alpha/3 and results[0]['mean'] > 100) if len(results)>0 and not pd.isna(results[0]['t_p']) else False
low_sig = (reject_bonf[1] and results[1]['t_p'] < alpha/3 and results[1]['mean'] < 100) if len(results)>1 and not pd.isna(results[1]['t_p']) else False
base_sig = (reject_bonf[2] and results[2]['t_p'] < alpha/3) if len(results)>2 and not pd.isna(results[2]['t_p']) else False
conclusions = []
if high_sig:
conclusions.append("HIGH: Istotne odchylenie w górę (micro-PK potwierdzone).")
else:
conclusions.append("HIGH: Brak istotnego odchylenia.")
if low_sig:
conclusions.append("LOW: Istotne odchylenie w dół (micro-PK potwierdzone).")
else:
conclusions.append("LOW: Brak istotnego odchylenia.")
if base_sig:
conclusions.append("BASELINE: Istotne odchylenie – możliwy bias RNG.")
else:
conclusions.append("BASELINE: Brak odchylenia – RNG działa poprawnie.")
return {
'alpha': alpha,
'alpha_corrected': alpha/3,
'results': summary,
'conclusions': conclusions,
'high_significant': high_sig,
'low_significant': low_sig,
'baseline_significant': base_sig
}
# ============================================================
# 4. GENEROWANIE WYKRESÓW
# ============================================================
def plot_results(df, output_file='micro_pk_plots.png'):
sns.set_style("whitegrid")
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
ax_hist_high = axes[0, 0]
ax_hist_low = axes[0, 1]
ax_hist_base = axes[1, 0]
ax_bar = axes[1, 1]
intentions = ['HIGH', 'LOW', 'BASELINE']
colors = {'HIGH': 'green', 'LOW': 'red', 'BASELINE': 'gray'}
means = []
ci_low = []
ci_high = []
for i, intent in enumerate(intentions):
subset = df[df['intention'].str.upper() == intent]['score']
if len(subset) == 0:
continue
if intent == 'HIGH':
ax = ax_hist_high
elif intent == 'LOW':
ax = ax_hist_low
else:
ax = ax_hist_base
ax.hist(subset, bins=30, density=True, alpha=0.6, color=colors[intent], edgecolor='black', label=f'{intent} (n={len(subset)})')
mu, std = subset.mean(), subset.std()
x = np.linspace(subset.min(), subset.max(), 100)
ax.plot(x, (1/(std*np.sqrt(2*np.pi)))*np.exp(-0.5*((x-mu)/std)**2), color='black', linestyle='--', linewidth=1.5)
ax.set_title(f'Warunek {intent}')
ax.set_xlabel('Wynik (liczba 1 w 200 bitach)')
ax.set_ylabel('Gęstość')
ax.legend()
mean = subset.mean()
sem = subset.std() / np.sqrt(len(subset))
ci = 1.96 * sem
means.append(mean)
ci_low.append(mean - ci)
ci_high.append(mean + ci)
x_pos = np.arange(len(intentions))
ax_bar.bar(x_pos, means, yerr=[mean - ci_low for mean, ci_low in zip(means, ci_low)],
capsize=5, color=[colors[i] for i in intentions], alpha=0.7, edgecolor='black')
ax_bar.axhline(y=100, color='black', linestyle='-', linewidth=1.5, label='Wartość oczekiwana (100)')
ax_bar.set_xticks(x_pos)
ax_bar.set_xticklabels(intentions)
ax_bar.set_ylabel('Średni wynik')
ax_bar.set_title('Średnie wyników z 95% przedziałem ufności')
ax_bar.legend()
plt.suptitle('Analiza wizualna eksperymentu micro-PK', fontsize=16)
plt.tight_layout()
plt.savefig(output_file, dpi=150)
plt.show()
print(f"Wykresy zapisano jako {output_file}")
# ============================================================
# 5. EKSPORT WYNIKÓW
# ============================================================
def export_results(analysis_results, input_filename, export_format='json', output_filename=None):
"""
Eksportuje wyniki analizy do pliku JSON lub CSV.
analysis_results: słownik zwrócony przez analyze()
input_filename: nazwa oryginalnego pliku z danymi (dla metadanych)
export_format: 'json' lub 'csv'
output_filename: jeśli None, generowany automatycznie
"""
if output_filename is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
base = os.path.splitext(os.path.basename(input_filename))[0]
output_filename = f"{base}_results_{timestamp}.{export_format}"
# Przygotowanie danych do eksportu
export_data = {
'metadata': {
'input_file': input_filename,
'analysis_timestamp': datetime.now().isoformat(),
'alpha': analysis_results['alpha'],
'alpha_corrected': analysis_results['alpha_corrected']
},
'conclusions': analysis_results['conclusions'],
'results': analysis_results['results']
}
if export_format == 'json':
with open(output_filename, 'w', encoding='utf-8') as f:
json.dump(export_data, f, indent=2, default=str)
print(f"Wyniki zapisano w formacie JSON: {output_filename}")
elif export_format == 'csv':
# Eksport tabeli wyników jako CSV
df_results = pd.DataFrame(analysis_results['results'])
df_results.to_csv(output_filename, index=False)
print(f"Wyniki zapisano w formacie CSV: {output_filename}")
# Dodatkowo zapisz wnioski w osobnym pliku .txt
txt_filename = output_filename.replace('.csv', '_conclusions.txt')
with open(txt_filename, 'w', encoding='utf-8') as f:
f.write(f"Micro-PK Analysis Conclusions\n")
f.write(f"Input file: {input_filename}\n")
f.write(f"Timestamp: {datetime.now().isoformat()}\n")
f.write(f"Alpha: {analysis_results['alpha']}, corrected: {analysis_results['alpha_corrected']}\n\n")
f.write("\n".join(analysis_results['conclusions']))
print(f"Wnioski zapisano w pliku: {txt_filename}")
else:
raise ValueError(f"Nieznany format eksportu: {export_format}")
# ============================================================
# 6. GŁÓWNA FUNKCJA STERUJĄCA
# ============================================================
def main():
parser = argparse.ArgumentParser(description='Analiza danych micro-PK z RNG')
parser.add_argument('--generate', type=str, help='Generuje przykładowy plik CSV (podaj nazwę pliku)')
parser.add_argument('--effect', type=float, default=0.5, help='Siła efektu dla generowanych danych (domyślnie 0.5)')
parser.add_argument('--file', type=str, help='Ścieżka do pliku CSV z danymi do analizy')
parser.add_argument('--plot', action='store_true', help='Generuje wykresy (wymaga --file)')
parser.add_argument('--alpha', type=float, default=0.05, help='Poziom istotności (domyślnie 0.05)')
parser.add_argument('--min_trials', type=int, default=1000, help='Minimalna liczba prób na warunek (domyślnie 1000)')
parser.add_argument('--export', type=str, choices=['json', 'csv'], help='Eksport wyników do pliku (json lub csv)')
parser.add_argument('--export_name', type=str, help='Nazwa pliku eksportu (opcjonalnie)')
args = parser.parse_args()
if args.generate:
generate_sample_data(args.generate, n_trials=10000, effect_strength=args.effect)
return
if args.file:
# Wczytanie danych
df = pd.read_csv(args.file)
# Walidacja
is_valid, issues = validate_data(df, min_trials_per_condition=args.min_trials)
if not is_valid:
print("\n=== BŁĘDY WALIDACJI ===")
for issue in issues:
print(f" ✗ {issue}")
print("\nAnaliza nie może być przeprowadzona. Popraw dane i spróbuj ponownie.")
return
else:
print("\n=== WALIDACJA ZAKOŃCZONA POMYŚLNIE ===")
for issue in issues: # issues są puste, ale mogą być ostrzeżenia?
if issue:
print(f" ⚠ {issue}")
print("Dane spełniają minimalne wymagania.")
# Analiza
analysis = analyze(df, alpha=args.alpha)
# Wydruk tabeli (powielamy część logiki z analyze, ale analyze już drukuje)
# analyze już drukuje, więc nie trzeba powtarzać, ale upewnijmy się że analyze zwraca też dane.
# W analyze mamy printy. Dla porządku wywołujemy analyze, a potem ewentualnie eksport.
# Jeśli chcemy mieć kontrolę nad drukowaniem, można by zmodyfikować analyze, ale na razie zostawiamy.
# Wywołamy analyze ponownie? Nie, analyze już zostało wywołane powyżej? Nie – bo analyze zostało wywołane dopiero co.
# W funkcji analyze są printy. Spoko.
# Eksport
if args.export:
export_results(analysis, args.file, export_format=args.export, output_filename=args.export_name)
# Wykresy
if args.plot:
plot_results(df)
return
parser.print_help()
if __name__ == "__main__":
main()Opis nowych funkcji:
- Walidacja – funkcja
validate_data():- Sprawdza, czy wyniki
scoremieszczą się w 0–200. - Czy nie brakuje wartości.
- Czy intencje są tylko HIGH, LOW, BASELINE.
- Czy liczba prób na każdy warunek jest >=
--min_trials(domyślnie 1000). - Czy kolumna
trialnie ma duplikatów. - Zwraca listę błędów/ostrzeżeń. Jeśli błędy krytyczne – analiza nie jest wykonywana.
- Sprawdza, czy wyniki
- Eksport – funkcja
export_results():- JSON – zawiera metadane, wnioski i tabelę wyników.
- CSV – zapisuje tabelę wyników, a dodatkowo tworzy plik
.txtz wnioskami. - Automatyczne generowanie nazwy pliku z datą, jeśli nie podano
--export_name.
- Nowe argumenty wiersza poleceń:
--min_trials– ustawia minimalną wymaganą liczbę prób na warunek.--export {json,csv}– włącza eksport.--export_name– własna nazwa pliku eksportu.
Przykłady użycia:
bash
# Wygenerowanie danych z efektem i analiza z eksportem JSON python micro_pk_analyzer.py --generate test.csv --effect 0.4 python micro_pk_analyzer.py --file test.csv --plot --export json # Analiza własnego pliku z minimalną liczbą prób 2000 na warunek python micro_pk_analyzer.py --file moje_dane.csv --min_trials 2000 --export csv --export_name moja_analiza # Sprawdzenie walidacji (np. gdy brakuje danych) python micro_pk_analyzer.py --file uszkodzone_dane.csv
Teraz skrypt jest w pełni profesjonalny – zapewnia kontrolę jakości danych i możliwość archiwizacji wyników. Możesz go bezpośrednio wkleić do artykułu jako aneks. Czy potrzebujesz jeszcze czegoś?
Dodaję do skryptu dwie kluczowe funkcjonalności: walidację danych wejściowych oraz eksport wyników analizy do pliku. Poniżej znajduje się kompletny, gotowy kod Pythona wraz z opisem nowych argumentów i przykładami użycia.
Pełny kod skryptu z walidacją i eksportem
python
"""
Micro-PK Data Analyzer – pełna wersja z generowaniem danych, walidacją, wykresami i eksportem
Autor: Zespół Ekspertów Psychotroniki
Licencja: do użytku profesjonalnego i badawczego
"""
import pandas as pd
import numpy as np
from scipy.stats import chisquare, ttest_1samp
from statsmodels.stats.multitest import multipletests
import matplotlib.pyplot as plt
import seaborn as sns
import argparse
import os
import json
from datetime import datetime
# ============================================================
# 1. GENEROWANIE PRZYKŁADOWYCH DANYCH (symulacja RNG)
# ============================================================
def generate_sample_data(filepath, n_trials=10000, effect_strength=0.0, seed=42):
"""
Generuje symulowane dane RNG.
effect_strength: odchylenie od wartości oczekiwanej 100 dla HIGH (dodatnie) i LOW (ujemne).
"""
np.random.seed(seed)
intentions = ['HIGH', 'LOW', 'BASELINE']
n_per_condition = n_trials // 3
data = []
trial_id = 1
for intent in intentions:
for _ in range(n_per_condition):
if intent == 'HIGH':
mean = 100 + effect_strength
elif intent == 'LOW':
mean = 100 - effect_strength
else:
mean = 100
score = int(np.random.normal(mean, 7.07))
score = max(0, min(200, score))
data.append([trial_id, intent, score])
trial_id += 1
df = pd.DataFrame(data, columns=['trial', 'intention', 'score'])
df.to_csv(filepath, index=False)
print(f"Wygenerowano przykładowy plik: {filepath}")
print(f"Liczba prób: {len(df)} (po {n_per_condition} na warunek)")
if effect_strength != 0:
print(f"Wprowadzono sztuczny efekt: HIGH mean = 100+{effect_strength}, LOW mean = 100-{effect_strength}")
return df
# ============================================================
# 2. WALIDACJA DANYCH
# ============================================================
def validate_data(df, min_trials_per_condition=1000):
"""
Przeprowadza walidację pliku danych:
- sprawdza, czy kolumny istnieją
- sprawdza, czy wartości score mieszczą się w 0-200
- sprawdza, czy nie brakuje wartości
- sprawdza, czy liczba prób na każdy warunek jest >= min_trials_per_condition
- wypisuje ostrzeżenia lub błędy
Zwraca (is_valid, warnings_errors_list)
"""
issues = []
# Sprawdzenie czy kolumny istnieją
required = {'trial', 'intention', 'score'}
if not required.issubset(df.columns):
issues.append(f"Brak wymaganych kolumn: {required - set(df.columns)}")
return False, issues
# Sprawdzenie typu i zakresu score
if not pd.api.types.is_numeric_dtype(df['score']):
issues.append("Kolumna 'score' nie jest numeryczna")
else:
if (df['score'] < 0).any() or (df['score'] > 200).any():
issues.append("Wartości 'score' poza zakresem 0-200")
# Brakujące wartości
if df[['trial', 'intention', 'score']].isnull().any().any():
issues.append("Występują brakujące wartości (NaN)")
# Sprawdzenie poprawności intencji
allowed_intentions = ['HIGH', 'LOW', 'BASELINE']
if not df['intention'].str.upper().isin(allowed_intentions).all():
issues.append("Niepoprawne wartości w kolumnie 'intention' (dozwolone: HIGH, LOW, BASELINE)")
# Liczba prób na warunek
counts = df['intention'].str.upper().value_counts()
for intent in allowed_intentions:
if intent not in counts:
issues.append(f"Brak warunku '{intent}' w danych")
elif counts[intent] < min_trials_per_condition:
issues.append(f"Warunek '{intent}' ma tylko {counts[intent]} prób, a wymagane minimum to {min_trials_per_condition}")
# Duplikaty trial?
if df['trial'].duplicated().any():
issues.append("Wartości w kolumnie 'trial' nie są unikalne")
is_valid = len(issues) == 0
return is_valid, issues
# ============================================================
# 3. FUNKCJE ANALITYCZNE
# ============================================================
def run_chi_square_grouped(scores, bins=20):
observed, bin_edges = np.histogram(scores, bins=bins, range=(0,200))
n = 200
probs_expected = []
for i in range(len(bin_edges)-1):
low = bin_edges[i]
high = bin_edges[i+1]
if i == len(bin_edges)-2:
prob = sum(np.math.comb(n, k) * (0.5**n) for k in range(int(low), int(high)+1))
else:
prob = sum(np.math.comb(n, k) * (0.5**n) for k in range(int(low), int(high)))
probs_expected.append(prob)
expected = np.array(probs_expected) * len(scores)
mask = expected > 0
chi2, p = chisquare(observed[mask], f_exp=expected[mask])
return chi2, p
def analyze(df, alpha=0.05):
intentions = ['HIGH', 'LOW', 'BASELINE']
results = []
p_values = []
for intent in intentions:
subset = df[df['intention'].str.upper() == intent]['score']
if len(subset) == 0:
results.append({'intention': intent, 'n': 0, 'mean': np.nan, 't_p': np.nan, 'chi2_p': np.nan})
p_values.append(np.nan)
continue
mean = subset.mean()
t_stat, t_p = ttest_1samp(subset, 100)
try:
chi2, chi_p = run_chi_square_grouped(subset)
except:
chi_p = np.nan
results.append({
'intention': intent,
'n': len(subset),
'mean': mean,
't_stat': t_stat,
't_p': t_p,
'chi2_p': chi_p
})
p_values.append(t_p)
# korekta Bonferroniego
reject_bonf, p_corrected, _, _ = multipletests(p_values, alpha=alpha, method='bonferroni', is_sorted=False)
# przygotowanie wyników dla raportu
summary = []
for i, res in enumerate(results):
is_sig = (reject_bonf[i] and res['t_p'] < alpha/3) if not pd.isna(res['t_p']) else False
summary.append({
'intention': res['intention'],
'n': res['n'],
'mean': res['mean'],
't_stat': res.get('t_stat', 0),
't_p': res['t_p'],
'chi2_p': res['chi2_p'],
'significant_bonferroni': is_sig
})
# Dodatkowe wnioski tekstowe
high_sig = (reject_bonf[0] and results[0]['t_p'] < alpha/3 and results[0]['mean'] > 100) if len(results)>0 and not pd.isna(results[0]['t_p']) else False
low_sig = (reject_bonf[1] and results[1]['t_p'] < alpha/3 and results[1]['mean'] < 100) if len(results)>1 and not pd.isna(results[1]['t_p']) else False
base_sig = (reject_bonf[2] and results[2]['t_p'] < alpha/3) if len(results)>2 and not pd.isna(results[2]['t_p']) else False
conclusions = []
if high_sig:
conclusions.append("HIGH: Istotne odchylenie w górę (micro-PK potwierdzone).")
else:
conclusions.append("HIGH: Brak istotnego odchylenia.")
if low_sig:
conclusions.append("LOW: Istotne odchylenie w dół (micro-PK potwierdzone).")
else:
conclusions.append("LOW: Brak istotnego odchylenia.")
if base_sig:
conclusions.append("BASELINE: Istotne odchylenie – możliwy bias RNG.")
else:
conclusions.append("BASELINE: Brak odchylenia – RNG działa poprawnie.")
# Wydruk tabeli
print("\n" + "="*80)
print("WYNIKI ANALIZY MICRO-PK")
print("="*80)
print(f"Poziom istotności α = {alpha}, korekta Bonferroniego → α_adj = {alpha/3:.4f}")
print("-"*80)
print(f"{'Intencja':<12} {'N':>8} {'Średnia':>10} {'t-stat':>10} {'p (t-test)':>12} {'Chi² p':>12} {'Istotne?':>12}")
print("-"*80)
for res in summary:
sig_str = "TAK (p<α_adj)" if res['significant_bonferroni'] else "NIE"
print(f"{res['intention']:<12} {res['n']:>8} {res['mean']:>10.3f} {res['t_stat']:>10.3f} {res['t_p']:>12.6f} {res['chi2_p']:>12.6f} {sig_str:>12}")
print("-"*80)
print("\nWNIOSKI:")
for c in conclusions:
print(f" {c}")
return {
'alpha': alpha,
'alpha_corrected': alpha/3,
'results': summary,
'conclusions': conclusions,
'high_significant': high_sig,
'low_significant': low_sig,
'baseline_significant': base_sig
}
# ============================================================
# 4. GENEROWANIE WYKRESÓW
# ============================================================
def plot_results(df, output_file='micro_pk_plots.png'):
sns.set_style("whitegrid")
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
ax_hist_high = axes[0, 0]
ax_hist_low = axes[0, 1]
ax_hist_base = axes[1, 0]
ax_bar = axes[1, 1]
intentions = ['HIGH', 'LOW', 'BASELINE']
colors = {'HIGH': 'green', 'LOW': 'red', 'BASELINE': 'gray'}
means = []
ci_low = []
ci_high = []
for i, intent in enumerate(intentions):
subset = df[df['intention'].str.upper() == intent]['score']
if len(subset) == 0:
continue
if intent == 'HIGH':
ax = ax_hist_high
elif intent == 'LOW':
ax = ax_hist_low
else:
ax = ax_hist_base
ax.hist(subset, bins=30, density=True, alpha=0.6, color=colors[intent], edgecolor='black', label=f'{intent} (n={len(subset)})')
mu, std = subset.mean(), subset.std()
x = np.linspace(subset.min(), subset.max(), 100)
ax.plot(x, (1/(std*np.sqrt(2*np.pi)))*np.exp(-0.5*((x-mu)/std)**2), color='black', linestyle='--', linewidth=1.5)
ax.set_title(f'Warunek {intent}')
ax.set_xlabel('Wynik (liczba 1 w 200 bitach)')
ax.set_ylabel('Gęstość')
ax.legend()
mean = subset.mean()
sem = subset.std() / np.sqrt(len(subset))
ci = 1.96 * sem
means.append(mean)
ci_low.append(mean - ci)
ci_high.append(mean + ci)
x_pos = np.arange(len(intentions))
ax_bar.bar(x_pos, means, yerr=[mean - ci_low for mean, ci_low in zip(means, ci_low)],
capsize=5, color=[colors[i] for i in intentions], alpha=0.7, edgecolor='black')
ax_bar.axhline(y=100, color='black', linestyle='-', linewidth=1.5, label='Wartość oczekiwana (100)')
ax_bar.set_xticks(x_pos)
ax_bar.set_xticklabels(intentions)
ax_bar.set_ylabel('Średni wynik')
ax_bar.set_title('Średnie wyników z 95% przedziałem ufności')
ax_bar.legend()
plt.suptitle('Analiza wizualna eksperymentu micro-PK', fontsize=16)
plt.tight_layout()
plt.savefig(output_file, dpi=150)
plt.show()
print(f"Wykresy zapisano jako {output_file}")
# ============================================================
# 5. EKSPORT WYNIKÓW
# ============================================================
def export_results(analysis_results, input_filename, export_format='json', output_filename=None):
"""
Eksportuje wyniki analizy do pliku JSON lub CSV.
analysis_results: słownik zwrócony przez analyze()
input_filename: nazwa oryginalnego pliku z danymi (dla metadanych)
export_format: 'json' lub 'csv'
output_filename: jeśli None, generowany automatycznie
"""
if output_filename is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
base = os.path.splitext(os.path.basename(input_filename))[0]
output_filename = f"{base}_results_{timestamp}.{export_format}"
# Przygotowanie danych do eksportu
export_data = {
'metadata': {
'input_file': input_filename,
'analysis_timestamp': datetime.now().isoformat(),
'alpha': analysis_results['alpha'],
'alpha_corrected': analysis_results['alpha_corrected']
},
'conclusions': analysis_results['conclusions'],
'results': analysis_results['results']
}
if export_format == 'json':
with open(output_filename, 'w', encoding='utf-8') as f:
json.dump(export_data, f, indent=2, default=str)
print(f"Wyniki zapisano w formacie JSON: {output_filename}")
elif export_format == 'csv':
# Eksport tabeli wyników jako CSV
df_results = pd.DataFrame(analysis_results['results'])
df_results.to_csv(output_filename, index=False)
print(f"Wyniki zapisano w formacie CSV: {output_filename}")
# Dodatkowo zapisz wnioski w osobnym pliku .txt
txt_filename = output_filename.replace('.csv', '_conclusions.txt')
with open(txt_filename, 'w', encoding='utf-8') as f:
f.write(f"Micro-PK Analysis Conclusions\n")
f.write(f"Input file: {input_filename}\n")
f.write(f"Timestamp: {datetime.now().isoformat()}\n")
f.write(f"Alpha: {analysis_results['alpha']}, corrected: {analysis_results['alpha_corrected']}\n\n")
f.write("\n".join(analysis_results['conclusions']))
print(f"Wnioski zapisano w pliku: {txt_filename}")
else:
raise ValueError(f"Nieznany format eksportu: {export_format}")
# ============================================================
# 6. GŁÓWNA FUNKCJA STERUJĄCA
# ============================================================
def main():
parser = argparse.ArgumentParser(description='Analiza danych micro-PK z RNG')
parser.add_argument('--generate', type=str, help='Generuje przykładowy plik CSV (podaj nazwę pliku)')
parser.add_argument('--effect', type=float, default=0.5, help='Siła efektu dla generowanych danych (domyślnie 0.5)')
parser.add_argument('--file', type=str, help='Ścieżka do pliku CSV z danymi do analizy')
parser.add_argument('--plot', action='store_true', help='Generuje wykresy (wymaga --file)')
parser.add_argument('--alpha', type=float, default=0.05, help='Poziom istotności (domyślnie 0.05)')
parser.add_argument('--min_trials', type=int, default=1000, help='Minimalna liczba prób na warunek (domyślnie 1000)')
parser.add_argument('--export', type=str, choices=['json', 'csv'], help='Eksport wyników do pliku (json lub csv)')
parser.add_argument('--export_name', type=str, help='Nazwa pliku eksportu (opcjonalnie)')
args = parser.parse_args()
if args.generate:
generate_sample_data(args.generate, n_trials=10000, effect_strength=args.effect)
return
if args.file:
# Wczytanie danych
df = pd.read_csv(args.file)
# Walidacja
is_valid, issues = validate_data(df, min_trials_per_condition=args.min_trials)
if not is_valid:
print("\n=== BŁĘDY WALIDACJI ===")
for issue in issues:
print(f" ✗ {issue}")
print("\nAnaliza nie może być przeprowadzona. Popraw dane i spróbuj ponownie.")
return
else:
print("\n=== WALIDACJA ZAKOŃCZONA POMYŚLNIE ===")
if issues:
for issue in issues:
print(f" ⚠ {issue}")
else:
print(" Wszystkie kontrole przeszły pomyślnie.")
# Analiza
analysis = analyze(df, alpha=args.alpha)
# Eksport
if args.export:
export_results(analysis, args.file, export_format=args.export, output_filename=args.export_name)
# Wykresy
if args.plot:
plot_results(df)
return
parser.print_help()
if __name__ == "__main__":
main()Nowe możliwości w skrócie
| Funkcja | Opis | Argument |
|---|---|---|
| Walidacja danych | Sprawdza, czy plik CSV ma wymagane kolumny, czy wyniki mieszczą się w 0–200, czy nie brakuje wartości, czy intencje są poprawne, czy liczba prób na warunek jest wystarczająca. | --min_trials N (domyślnie 1000) |
| Eksport wyników | Zapisuje wyniki analizy do pliku JSON (pełne dane) lub CSV (tabela) + osobny plik z wnioskami. | --export json lub --export |
| Niestandardowa nazwa eksportu | Pozwala określić własną nazwę pliku eksportu. | --export_name moj_raport.json |
Przykłady użycia
1. Generowanie danych testowych z efektem
bash
python micro_pk_analyzer.py --generate moje_dane.csv --effect 0.3
2. Analiza własnego pliku z walidacją, wykresami i eksportem JSON
bash
python micro_pk_analyzer.py --file eksperyment.csv --plot --export json --min_trials 2000
3. Analiza z eksportem CSV i własną nazwą pliku
bash
python micro_pk_analyzer.py --file eksperyment.csv --export csv --export_name wyniki_mojego_badania
4. Sama walidacja (bez analizy? nie – analiza uruchamia się zawsze, ale jeśli walidacja nie przejdzie, skrypt kończy działanie)
bash
python micro_pk_analyzer.py --file podejrzane_dane.csv
Struktura pliku eksportu
Dla JSON:
json
{
"metadata": {
"input_file": "eksperyment.csv",
"analysis_timestamp": "2026-06-07T14:32:00",
"alpha": 0.05,
"alpha_corrected": 0.016666666666666666
},
"conclusions": [
"HIGH: Istotne odchylenie w górę (micro-PK potwierdzone).",
"LOW: Brak istotnego odchylenia.",
"BASELINE: Brak odchylenia – RNG działa poprawnie."
],
"results": [
{"intention": "HIGH", "n": 3333, "mean": 100.42, "t_stat": 3.21, "t_p": 0.0013, "chi2_p": 0.32, "significant_bonferroni": true},
...
]
}Dla CSV:
Plik eksperyment_results_20260607_143200.csv zawiera tabelę z wynikami.
Plik eksperyment_results_20260607_143200_conclusions.txt zawiera wnioski.