Jeśli szukasz rzetelnego bukmachera w Polsce do zakładów sportowych i kasyn online, Mostbet jest właściwym wyborem. Wśród jego zalet: urozmaicona oferta wydarzeń, wysokie kursy, różne bonusy i promocje, a także darmowe zakłady i darmowe spiny. Znajdź kod promocyjny Mostbet, który zwiększy Twoją kwotę bonusu. A żeby nie przegapić ani chwili gry, pobierz naszą wygodną aplikację mobilną!

Aneks: Automatyczna analiza statystyczna dla protokołu 10⁴ prób (RNG)

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

  1. Przygotuj dane w pliku CSV o nazwie np. moj_eksperyment.csv o strukturze:

csv

trial,intention,score
1,HIGH,101
2,LOW,98
3,BASELINE,100
4,HIGH,103
...
  1. Zapisz powyższy kod w pliku micro_pk_analyzer.py.
  2. 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:

  1. Funkcją generowania przykładowego pliku CSV (z symulowanymi danymi z efektem micro-PK lub bez).
  2. Funkcją rysowania profesjonalnych wykresów (histogramy dla każdego warunku, porównanie średnich z przedziałami ufności).
  3. 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, LOW lub BASELINE
  • score – 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.3 wprowadza 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
  • --plot generuje wykresy (zapisuje jako micro_pk_plots.png i 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ę:

  1. Histogramy dla HIGH, LOW, BASELINE – każdy z dopasowaną krzywą normalną. Pozwalają ocenić, czy rozkład wyników odbiega od oczekiwanego (symetryczny, dzwonowy).
  2. 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:

  1. Eksport wyników analizy do pliku (JSON, CSV lub obu) – aby można było zapisać tabelę z wynikami, wnioski i parametry analizy.
  2. 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:

  1. Walidacja – funkcja validate_data():
    • Sprawdza, czy wyniki score mieszczą 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 trial nie ma duplikatów.
    • Zwraca listę błędów/ostrzeżeń. Jeśli błędy krytyczne – analiza nie jest wykonywana.
  2. Eksport – funkcja export_results():
    • JSON – zawiera metadane, wnioski i tabelę wyników.
    • CSV – zapisuje tabelę wyników, a dodatkowo tworzy plik .txt z wnioskami.
    • Automatyczne generowanie nazwy pliku z datą, jeśli nie podano --export_name.
  3. 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

FunkcjaOpisArgument
Walidacja danychSprawdza, 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ówZapisuje wyniki analizy do pliku JSON (pełne dane) lub CSV (tabela) + osobny plik z wnioskami.--export json lub --export
csv
Niestandardowa nazwa eksportuPozwala 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.