Per farsi un'idea di come «scrivere un'estensione», rispetto all'approccio diretto visto nei capitoli precedenti, creeremo ancora una volta l'estensione del test t completo - questa volta solo con le funzioni di R del pacchetto rkwarddev.
Suggerimento
Il pacchetto aggiungerà una nuova finestra della GUI per RKWard sotto → → . Come suggerisce il nome, è possibile creare scheletri di estensioni per ulteriori modifiche. Questa finestra stessa è stata a sua volta generata da uno script rkwarddev che potete trovare nella cartella «demo» del pacchetto installato e nei sorgenti del pacchetto, come ulteriore esempio. Potete anche eseguirlo invocando demo("skeleton_dialog")
Noterete immediatamente che il flusso di lavoro è notevolmente diverso: contrariamente alla scrittura diretta del codice XML, non inizia con la definizione di <document>, ma direttamente con gli elementi dell'estensione che vorreste avere nella finestra. Potete assegnare ciascun elemento dell'interfaccia (che si tratti di caselle di selezione, menu a tendina, slot variabili o qualsiasi altra cosa) a singoli oggetti di R, e poi combinare questi oggetti nella GUI effettiva. Il pacchetto ha funzioni per ogni tag XML che può essere utilizzato per definire la GUI dell'estensione e la maggior parte di essi ha anche lo stesso nome, solo con il prefisso rk.XML.*. Ad esempio, la definizione di un elemento <varselector> e di due elementi <varslot> per le variabili "x" e "y" dell'esempio di t-test può essere eseguita nel modo seguente:
variables <- rk.XML.varselector(id.name="vars")
var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE, id.name="x")
var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE, id.name="y")
Il dettaglio più interessante sono probabilmente le source=variables: una caratteristica importante del pacchetto è che tutte le funzioni possono generare ID automatici, quindi non dovete preoccuparvi di pensare ai valori id o di ricordarli per fare riferimento a uno specifico elemento dell'estensione. Potete semplicemente dare gli oggetti di R come riferimento, poiché tutte le funzioni che necessitano di un ID da qualche altro elemento possono leggerlo anche da questi oggetti. rk.XML.varselector() è un po' speciale, poiché di solito non ha un contenuto specifico da cui creare un ID (può, ma solo se specificate un'etichetta), quindi dobbiamo impostare un nome ID. Ma rk.XML.varslot() non avrebbe bisogno degli argomenti id.name qui, quindi sarebbe sufficiente:
variables <- rk.XML.varselector(id.name="vars")
var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE)
var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE)
Per ricreare il codice di esempio sul punto, è necessario impostare manualmente tutti i valori di ID. Ma dal momento che il pacchetto renderà le nostre vite più facili, d'ora in poi non ce ne occuperemo più.
Suggerimento
rkwarddev è capace di molta automazione per aiutarti a costruire le proprie estensioni. Tuttavia, potrebbe essere preferibile non utilizzarlo fino in fondo. Se l'obiettivo è produrre un codice che non solo funzioni, ma che possa essere facilmente letto e confrontato con lo script del generatore da un essere umano, si dovrebbe considerare di impostare sempre ID utili con id.name. Assegnare un nome ai propri oggetti R identici a questi ID aiuteranno anche a ottenere il codice dello script che è facile da comprendere.
Se si vuole vedere come appare il codice XML dell'elemento definito se lo si è esportato in un file, è possibile semplicemente chiamare l'oggetto con il suo nome. Quindi, se si è invocata «var.x» nella propria sessione di R, si dovrebbe vedere qualcosa del genere:
<varslot id="vrsl_compare" label="compare" source="vars" types="number" required="true" />
Alcuni tag sono utili solo nel contesto di altri. Pertanto, ad esempio, non troverete una funzione per il tag <opzione>. Invece, sia i pulsanti di opzione che gli elenchi a tendina sono definiti includendo le loro opzioni come un elenco denominato, dove i nomi rappresentano le etichette da mostrare nella finestra e il loro valore è un vettore denominato che può avere due voci, val per il valore di un'opzione e il booleano chk per specificare se questa opzione è selezionata per impostazione predefinita.
test.hypothesis <- rk.XML.radio("ipotesi del test in uso",
options=list(
"Due lati"=c(val="due.lati"),
"Il primo è più grande"=c(val="maggiore"),
"Il secondo è più grande"=c(val="minore")
)
)
Il risultato è simile al seguente:
<radio id="rad_usngtsth" label="ipotesi di test in uso">
<option label="Due lati" value="due.lati" />
<option label="Il primo è più grande" value="maggiore" />
<option label="Il secondo è più grande" value="minore" />
</radio>
Tutto ciò che manca agli elementi della scheda «Impostazioni di base» è la casella di controllo per i campioni accoppiati e la strutturazione di tutti questi elementi in righe e colonne:
check.paired <- rk.XML.cbox("Campione accoppiato", value="1", un.value="0")
basic.settings <- rk.XML.row(variables, rk.XML.col(var.x, var.y, test.hypothesis, check.paired))
rk.XML.cbox() è un'eccezione rara in cui il nome della funzione non contiene il nome completo del tag, per risparmiare battiture per questo elemento spesso utilizzato. Questo è ciò che basic.settings ora contiene:
<row id="row_vTFSPP10TF">
<varselector id="vars" />
<column id="clm_vrsTFSPP10">
<varslot id="vrsl_compare" label="compare" source="vars" types="number" required="true" />
<varslot id="vrsl_against" label="against" i18n_context="compare against" source="vars" types="number" required="true" />
<radio id="rad_usngtsth" label="ipotesi di test in uso">
<option label="Due lati" value="due.lati" />
<option label="Il primo è più grande" value="maggiore" />
<option label="Il secondo è più grande" value="minore" />
</radio>
<checkbox id="chc_Pardsmpl" label="Campione accoppiato" value="1" value_unchecked="0" />
</column>
</row>
In modo simile, le righe successive creeranno oggetti di R per gli elementi della scheda «Opzioni», introducendo le funzioni per caselle numeriche, cornici e allungamento:
check.eqvar <- rk.XML.cbox("assumi varianze uguali equal variances", value="1", un.value="0")
conf.level <- rk.XML.spinbox("livello di confidenza", min=0, max=1, initial=0.95)
check.conf <- rk.XML.cbox("stampa intervallo di confidenza", val="1", chk=TRUE)
conf.frame <- rk.XML.frame(conf.level, check.conf, rk.XML.stretch(), label="Intervallo di confidenza")
Ora tutto ciò che dobbiamo fare è riunire gli oggetti in un tabbook e posizionarli in una sezione di dialogo:
full.dialog <- rk.XML.dialog(
label="Test t a due variabili",
rk.XML.tabbook(tabs=list("Basic settings"=basic.settings, "Options"=list(check.eqvar, conf.frame)))
)
Possiamo inoltre creare la sezione della procedura guidata con le sue due pagine utilizzando gli stessi oggetti, quindi i loro ID saranno estratti per i tag <copy>:
full.wizard <- rk.XML.wizard(
label="Test t a due variabili",
rk.XML.page(
rk.XML.text("Come primo passo, seleziona le due variabili che desideri confrontare.
E specifica quale ipotizzi sia la più grande. Seleziona due lati,
se la tua teoria non indica quale variabile sia più grande."),
rk.XML.copy(basic.settings)),
rk.XML.page(
rk.XML.text("Di seguito, sono disponibili alcune opzioni avanzate. È generalmente sicuro non assumere che
le variabili abbiano varianze uguali. Una correzione appropriata sarà quindi applicata.
La scelta di \"assumi varianze uguali\" potrebbe aumentare la potenza del test, tuttavia."),
rk.XML.copy(check.eqvar),
rk.XML.text("A volte è utile ottenere una stima dell'intervallo di confidenza della
differenza tra le medie. Sotto, puoi specificare se una dovrebbe essere mostrata, e
quale livello di confidenza dovrebbe essere applicato (95% corrisponde a un livello di significatività
del 5%)."),
rk.XML.copy(conf.frame)))
Questo è tutto per la GUI. Il documento globale sarà combinato alla fine da rk.plugin.skeleton().
Fino ad ora, l'utilizzo del pacchetto rkwarddev potrebbe non aver essere stato di aiuto. Questo cambierà proprio ora.
Prima di tutto, proprio come non dovevamo preoccuparci degli ID per gli elementi quando si definisce la struttura della GUI, non dobbiamo preoccuparci dei nomi delle variabili JavaScript nel passaggio successivo. Se si vuole maggior controllo, è possibile scrivere codice JavaScript e incollarlo nel file generato. Ma è probabilmente molto più efficiente per farlo alla maniera di rkwarddev.
In particolare non dovete definire alcuna variabile tu stesso, dato che rk.plugin.skeleton() può analizzare il vostro codice XML e definire automaticamente tutte le variabili di cui probabilmente avrete bisogno: ad esempio, non vi preoccuperete di includere una casella di selezione se non ne utilizzate il valore o lo stato in seguito. Quindi possiamo iniziare a scrivere immediatamente l'effettivo codice R che genera JS.
Suggerimento
La funzione rk.JS.scan() può anche eseguire la scansione dei file XML alla ricerca delle variabili.
Il pacchetto ha alcune funzioni per i costrutti del codice JS comunemente usati nelle estensioni di RKWard, come la funzione echo() o le condizioni if() {...} else {...}. Ci sono alcune differenze tra JS e R, ad es., per paste() in R userete la virgola per concatenare stringhe di caratteri, mentre per echo() in JS userete «+» e le righe devono terminare con un punto e virgola. Utilizzando il pulsante R funzioni, potete quasi dimenticare queste differenze e continuare a scrivere codice R.
Queste funzioni possono accettare diverse classi di oggetti di input: testo semplice, oggetti di R con codice XML come sopra o, a sua volta, risultati di alcune altre funzioni JS del pacchetto. Alla fine, invocherete sempre rk.paste.JS(), che si comporta in modo simile a paste(), ma a seconda degli oggetti di input li sostituirà con i loro ID XML, nome della variabile JavaScript o anche blocchi di codice JavaScript completi.
Per l'esempio del test t, abbiamo bisogno di due oggetti JS: uno per calcolare i risultati e uno per stamparli nella funzione printout():
JS.calc <- rk.paste.JS(
echo("res <- t.test (x=", var.x, ", y=", var.y, ", hypothesis=\"", test.hypothesis, "\""),
js(
if(check.paired){
echo(", paired=TRUE")
},
if(!check.paired && check.eqvar){
echo(", var.equal=TRUE")
},
if(conf.level != "0.95"){
echo(", conf.level=", conf.level)
},
linebreaks=TRUE
),
echo(")\n"),
level=2
)
JS.print <- rk.paste.JS(echo("rk.print (res)\n"), level=2)
Come potete vedere, rkwarddev fornisce anche un implementazione di R della funzione echo(). Restituisce esattamente una stringa di caratteri con una versione JS valida di se stesso. Potreste anche notare che tutti gli oggetti di R qui sono quelli che abbiamo creato in precedenza. Saranno automaticamente sostituiti con i nomi delle variabili, quindi dovrebbe essere abbastanza intuitivo. Ogni volta che è necessaria solo questa sostituzione, è possibile utilizzare la funzione id(), che restituirà anche esattamente una stringa di caratteri da tutti gli oggetti che le sono stati forniti (si potrebbe dire che si comporta come paste() con una sostituzione di oggetto molto specifica).
La funzione js() è un wrapper che ti consente di utilizzare le condizioni if(){...} else {...} di R come sei abituato. Saranno tradotti direttamente nel codice JS. Conserva anche alcuni operatori come <, >= o ||, quindi puoi confrontare logicamente i tuoi oggetti di R senza bisogno di virgolette per la maggior parte del tempo. Diamo un'occhiata all'oggetto «JS.calc» risultante, che ora contiene una stringa di caratteri con questo contenuto:
echo("res <- t.test (x=" + vrslCompare + ", y=" + vrslAgainst + ", hypothesis=\"" + radUsngtsth + "\"");
if(chcPardsmpl) {
echo(", paired=TRUE");
} else {}
if(!chcPardsmpl && chcAssmqlvr) {
echo(", var.equal=TRUE");
} else {}
if(spnCnfdnclv != "0.95") {
echo(", conf.level=" + spnCnfdnclv);
} else {}
echo(")\n");
Nota
In alternativa per le condizioni if() nidificate in js(), è possibile utilizzare la funzione ite(), che si comporta in modo simile alla ifelse() di R. Tuttavia, le istruzioni condizionali costruite usando ite() di solito sono più difficili da leggere e dovrebbero essere sostituite da js() quando possibile.
Questa sezione è molto breve: non abbiamo bisogno di scrivere alcuna .pluginmap, in quanto può essere generata automaticamente da rk.plugin.skeleton(). La gerarchia dei menu può essere specificata tramite l'opzione pluginmap:
[...]
pluginmap=list(
name="Test t a due variabili",
hierarchy=list("analysis", "means", "t-Test"))
[...]
Anche questa sezione è molto breve: rk.plugin.skeleton() non può scrivere un'intera pagina della guida che ha. Ma può analizzare il documento XML anche per gli elementi che probabilmente meritano una voce nella pagina della guida e crea automaticamente un modello di pagina della guida per la nostra estensione. Tutto quello che dobbiamo fare in seguito è scrivere alcune righe per ciascuna sezione elencata.
Suggerimento
La funzione rk.rkh.scan() può anche eseguire la scansione dei file XML esistenti per creare uno scheletro di file della guida.
Ora arriva il passaggio finale, in cui consegneremo tutti gli oggetti generati a rk.plugin.skeleton():
plugin.dir <- rk.plugin.skeleton("t-Test",
xml=list(
dialog=full.dialog,
wizard=full.wizard),
js=list(
results.header="Test t a due variabili",
calculate=JS.calc,
printout=JS.print),
pluginmap=list(
name="Test t a due variabili",
hierarchy=list("analysis", "means", "t-Test")),
load=TRUE,
edit=TRUE,
show=TRUE)
I file saranno creati in una cartella temporanea per impostazione predefinita. Le ultime tre opzioni non sono necessarie, ma molto utili: load=TRUE aggiungerà automaticamente la nuova estensione alla configurazione di RKWard (poiché è in una cartella temporanea e quindi cesserà di esistere alla chiusura di RKWard, sarà automaticamente rimossa da RKWard al suo prossimo avvio), edit=TRUE aprirà tutti i file creati per la modifica nelle schede dell'editor di RKWard e show=TRUE tenterà di avviare direttamente l'estensione, in modo da poter esaminare come appare senza un clic. Potreste prendere in considerazione l'aggiunta di overwrite=TRUE se state per eseguire lo script ripetutamente (ad es. dopo modifiche al codice), poiché per impostazione predefinita nessun file sarà sovrascritto.
L'oggetto risultato «plugin.dir» contiene il percorso della cartella in cui è stata creata l'estensione. Questo può essere utile in combinazione con la funzione rk.build.package(), per creare un vero e proprio pacchetto di R per condividere la propria estensione con altri -- ad es. inviandola alla squadra di sviluppo di RKWard da aggiungere al nostro repository di estensioni.
Per ricapitolare quanto sopra, ecco lo script completo per creare l'esempio di test t funzionante. In aggiunta al codice già spiegato, viene caricato anche il pacchetto, se necessario, e utilizza l'ambiente local(), quindi tutti gli oggetti creati non finiranno nel tuo attuale spazio di lavoro (eccetto «plugin.dir»):
require(rkwarddev)
local({
variables <- rk.XML.varselector(id.name="vars")
var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE)
var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE)
test.hypothesis <- rk.XML.radio("ipotesi del test in uso",
options=list(
"Due lati"=c(val="due.lati"),
"Il primo è più grande"=c(val="maggiore"),
"Il secondo è più grande"=c(val="minore")
)
)
check.paired <- rk.XML.cbox("Campione accoppiato", value="1", un.value="0")
basic.settings <- rk.XML.row(variables, rk.XML.col(var.x, var.y, test.hypothesis, check.paired))
check.eqvar <- rk.XML.cbox("assumi varianza uguale", value="1", un.value="0")
conf.level <- rk.XML.spinbox("livello di confidenza", min=0, max=1, initial=0.95)
check.conf <- rk.XML.cbox("stampa intervallo di confidenza", val="1", chk=TRUE)
conf.frame <- rk.XML.frame(conf.level, check.conf, rk.XML.stretch(), label="Intervallo di confidenza")
full.dialog <- rk.XML.dialog(
label="Two Variable t-Test",
rk.XML.tabbook(tabs=list("Impostazioni di base"=basic.settings, "Options"=list(check.eqvar, conf.frame)))
)
full.wizard <- rk.XML.wizard(
label="Test t a due variabili",
rk.XML.page(
rk.XML.text("Come primo passo, seleziona le due variabili che desideri confrontare.
E specifica, quale ipotizzi essere la più grande. Seleziona due lati,
se la tua teoria non lo indica, quale variabile è più grande."),
rk.XML.copy(basic.settings)),
rk.XML.page(
rk.XML.text("Di seguito sono disponibili alcune opzioni avanzate. È generalmente sicuro non assumere che le
variabili abbiano varianze uguali. Una correzione appropriata sarà quindi applicata.
Tuttavia, la scelta di \"assumi varianze uguali\" potrebbe incrementare la potenza del test."),
rk.XML.copy(check.eqvar),
rk.XML.text("A volte è utile ottenere una stima dell'intervallo di confidenza della
differenza tra le medie. Sotto, puoi specificare se una dovrebbe essere mostrata, e
quale livello di confidenza dovrebbe essere applicata (95% corrisponde a un livello di significatività
del 5%)."),
rk.XML.copy(conf.frame)))
JS.calc <- rk.paste.JS(
echo("res <- t.test (x=", var.x, ", y=", var.y, ", hypothesis=\"", test.hypothesis, "\""),
js(
if(check.paired){
echo(", paired=TRUE")
},
if(!check.paired && check.eqvar){
echo(", var.equal=TRUE")
},
if(conf.level != "0.95"){
echo(", conf.level=", conf.level)
},
linebreaks=TRUE
),
echo(")\n"), level=2)
JS.print <- rk.paste.JS(echo("rk.print (res)\n"), level=2)
plugin.dir <<- rk.plugin.skeleton("Test t",
xml=list(
dialog=full.dialog,
wizard=full.wizard),
js=list(
results.header="Test t a due variabili",
calculate=JS.calc,
printout=JS.print),
pluginmap=list(
name="Test t a due variabili",
hierarchy=list("analysis", "means", "t-Test")),
load=TRUE,
edit=TRUE,
show=TRUE,
overwrite=TRUE)
})