Praktiskt exempel

För att ge dig en idé om hur det ser ut att skapa ett insticksprogram med skript, jämfört med den direkta metod som du har sett i tidigare kapitel, skapar vi hela t-test insticksprogrammet igen, denna gång bara med R-funktionerna i paketet rkwarddev.

Tips

Paketet lägger till en ny dialogruta i det grafiska användargränssnittet i RKWard under ArkivExportSkapa RKWard insticksprogramskript. Som namnet anger, kan insticksprogrammallar för vidare redigering skapas med det. Dialogrutan själv skapades i sin tur av ett rkwarddev-skript, som finns i katalogen demo i det installerade paketet och paketkällkoden, som ett ytterligare exempel. Det går också att köra det genom att anropa demo("skeleton_dialog").

Beskrivning av det grafiska användargränssnittet

Man märker omedelbart att arbetsflödet är betydligt annorlunda: I motsats till att skriva XML-koden direkt, börjar man inte med definitionen av <document>, utan direkt med elementen i insticksprogram som man vill ha i dialogrutan. Det går att tilldela alla gränssnittselement, vare sig de är kryssrutor, kombinationsmenyer, variabelplatser eller någonting annat, till individuella R-objekt och därefter kombinera dessa objekt till det verkliga grafiska användargränssnittet. Paketet har funktioner för varje XML-tagg som kan användas för att definiera insticksprogrammets grafiska användargränssnitt och de flesta har till och med samma namn, förutom prefixet rk.XML.*. Att exempelvis definiera ett <varselector>- och två <varslot>-element för variablerna "x" och "y" i t-testen kan göras med:

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")
                

Den mest intressanta detaljen är troligen source=variabler: En framträdande funktion i paketet är att alla funktioner kan skapa automatiska id, så att man inte behöver vare sig bry sig om att tänka på id-värden eller komma ihåg dem för att referera till ett specifikt element i insticksprogrammet. Man kan helt enkelt ange R-objekten som referens, eftersom alla funktioner som behöver en id från något annat element också kan läsa det från dessa objekt. rk.XML.varselector() är något speciell, eftersom den oftast inte har något särskilt innehåll att skapa en id från (den kan göra det, men bara om du anger en beteckning), så vi måste ange ett id-namn. Men rk.XML.varslot() skulle inte behöva argumenten id.name här, så följande skulle vara nog:

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)
                

För att återskapa exempelkoden exakt, skulle alla id värden behöva ställas in för hand. Men eftersom paketet är avsett att göra livet enklare, bryr vi oss inte om det längre.

Tips

rkwarddev har möjlighet att automatisera en hel del för att hjälpa till att skapa insticksprogram. Dock kanske det är att föredra att inte använda det fullt ut. Om målet är att skapa kod som inte bara fungerar, men är lättläst och jämförbart med genereringsskriptet av en person, bör man fundera på att alltid ange användbara id med id.name. Namngivning av R-objekt identiska med dessa id, hjälper också för att få skriptkod som är lätt att förstå.

Om man vill se hur XML-koden för det definierade elementet ser ut om det exporterades till en fil, kan objektet bara anropas enligt namn. Om man nu anropar var.x i R-sessionen, bör man se någonting som liknar det här:

<varslot id="vrsl_compare" label="compare" source="vars" types="number" required="true" />
                

Vissa taggar är bara användbara i sammanhang med andra. Därför finns exempelvis inte någon funktion för taggen <option>. Istället definieras både alternativknappar och kombinationslistor så att deras alternativ inkluderas som en namngiven lista, där namnen representerar beteckningar som ska visas i dialogrutan, och deras värden är en namngiven vektor som kan ha två poster, val för alternativets värde och Boolean chk för att ange att alternativet normalt ska kontrolleras.

test.hypothesis <- rk.XML.radio("using test hypothesis",
        options=list(
                "Two-sided"=c(val="two.sided"),
                "First is greater"=c(val="greater"),
                "Second is greater"=c(val="less")
        )
)
                

Resultatet ser ut så här:

<radio id="rad_usngtsth" label="using test hypothesis">
        <option label="Two-sided" value="two.sided" />
        <option label="First is greater" value="greater" />
        <option label="Second is greater" value="less" />
</radio>
                

Allt som saknas för elementen under fliken Basic settings är kryssrutan för parade samplingar, och strukturering av alla elementen i rader och kolumner.

check.paired <- rk.XML.cbox("Paired sample", 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() är ett ovanligt undantag där funktionsnamnet inte innehåller hela taggnamnet, för att minska skrivbördan för det här ofta använda elementet. Det här är vad basic.settings nu innehåller:

<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="using test hypothesis">
                        <option label="Two-sided" value="two.sided" />
                        <option label="First is greater" value="greater" />
                        <option label="Second is greater" value="less" />
                </radio>
                <checkbox id="chc_Pardsmpl" label="Paired sample" value="1" value_unchecked="0" />
        </column>
</row>
                

På ett liknande sätt, skapar följande rader R-objekt för elementen under fliken Options, inklusive funktioner för nummerrutor, ramar och utsträckning:

check.eqvar <- rk.XML.cbox("assume equal variances", value="1", un.value="0")
conf.level <- rk.XML.spinbox("confidence level", min=0, max=1, initial=0.95)
check.conf <- rk.XML.cbox("print confidence interval", val="1", chk=TRUE)
conf.frame <- rk.XML.frame(conf.level, check.conf, rk.XML.stretch(), label="Confidence Interval")
                

Nu är allt vi behöver göra att lägga ihop objekten i en flikbok, och placera den i en dialogsektion:

full.dialog <- rk.XML.dialog(
        label="Two Variable t-Test",
        rk.XML.tabbook(tabs=list("Basic settings"=basic.settings, "Options"=list(check.eqvar, conf.frame)))
)
                

Vi kan också skapa guidesektionen med dess två sidor genom att använda samma objekt, så att deras id extraheras för taggarna <copy>:

full.wizard <- rk.XML.wizard(
                label="Two Variable t-Test",
                rk.XML.page(
                        rk.XML.text("As a first step, select the two variables you want to compare against
                                each other. And specify, which one you theorize to be greater. Select two-sided,
                                if your theory does not tell you, which variable is greater."),
                        rk.XML.copy(basic.settings)),
                rk.XML.page(
                        rk.XML.text("Below are some advanced options. It's generally safe not to assume the
                                variables have equal variances. An appropriate correction will be applied then.
                                Choosing \"assume equal variances\" may increase test-strength, however."),
                        rk.XML.copy(check.eqvar),
                        rk.XML.text("Sometimes it's helpful to get an estimate of the confidence interval of
                                the difference in means. Below you can specify whether one should be shown, and
                                which confidence-level should be applied (95% corresponds to a 5% level of
                                significance)."),
                        rk.XML.copy(conf.frame)))
                

Det är allt för det grafiska användargränssnittet. Det globala dokumentet kombineras till sist av rk.plugin.skeleton().

JavaScript-kod

Hittills kanske det inte verkar som användning av paketet rkwarddev har hjälpt så mycket. Det kommer att ändras nu.

För det första, precis som vi inte behövde bry oss om id för elementen när layouten av det grafiska användargränssnittet definierades, behöver vi inte bry oss om namn på JavaScript-variabler i nästa steg. Om man vill ha större kontroll, kan man skriva vanlig JavaScript-kod och få den inklistrad i den genererade filen. Men det är troligen mycket effektivare att göra det på sättet som i rkwarddev.

Framför allt behöver man inte definiera några variabler själv, eftersom rk.plugin.skeleton() kan söka igenom XML-koden och automatiskt definiera alla variabler som troligen behövs. Man skulle exempelvis inte bry sig om att inkludera en kryssruta om inte dess värde eller tillstånd senare används. Så vi kan börja skriva den verkliga R-koden som skapar JS omedelbart.

Tips

Funktionen rk.JS.scan() kan också söka igenom befintliga XML-filer efter variabler.

Paketet har några funktioner för JS-kodkonstruktioner som vanligtvis används i RKWard-insticksprogram, såsom funktionen echo() eller villkor med if() {...} else {...}. Det finns några skillnader mellan JS och R, t.ex. används kommatecken för att konkatenera teckensträngar för funktionen paste() i R, medan för echo() i JS används +, och rader måste sluta med ett semikolon. Genom att använda R-funktionerna kan man nästan glömma bort skillnaderna och fortsätta skriva R-kod.

Funktionerna kan acceptera olika klasser av indataobjekt: Antingen vanlig text, R-objekt med XML-kod som ovan, eller i sin tur resultat från några andra JS-funktioner i paketet. I slutändan måste alltid rk.paste.JS() anropas, som beter sig på liknande sätt som paste(), men beroende på indataobjekt, ersätter dem med deras XML id, JavaScript variabelnamn eller till och med fullständiga JavaScript kodblock.

För t-test exemplet behöver vi två JS-objekt: Ett för att beräkna resultatet, och ett för att skriva ut dem i funktionen 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)
                

Som du kan se, tillhandahåller rkwarddev också en R-implementering av funktionen echo(). Den returnerar exakt en teckensträng med en giltig JS-version av sig själv. Du kanske också märker att alla R-objekten här är de vi skapade tidigare. De ersätts automatiskt av sina variabelnamn, så det bör vara riktigt intuitivt. Så snart bara den här ersättningen behövs, kan funktionen id() användas, som också returnerar exakt en teckensträng för alla objekt som anges (man kan säga att den beter sig som paste() med en mycket specifik objektsubstitution).

Funktionen js() är en omgivande funktion som låter dig använda R-villkor, if(){...} else {...} som du är van vid. De översätts direkt till JS-kod. Det bevarar också vissa operatorer som <, >= och ||, så det går att jämföra R-objekten logiskt utan behov av citering för det mesta. Låt oss ta en titt på det resulterade objektet JS.calc, som nu har en teckensträng med följande innehåll:

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");
                

Notera

Som alternativ till if()-villkor nästlade i js(), kan man använda funktionen ite(), som beter sig på liknande sätt som ifelse() i R. Dock är det oftast svårare att läsa villkorssatser skapade med ite(), och de bör ersättas med js() så fort det är möjligt.

Insticksavbildning

Det här avsnittet är mycket kort: Vi behöver inte skriva en .pluginmap alls, eftersom den kan skapas automatiskt av rk.plugin.skeleton(). Menyhierarkin kan anges via alternativet pluginmap:

[...]
        pluginmap=list(
                name="Two Variable t-Test",
                hierarchy=list("analysis", "means", "t-Test"))
        [...]
                        

Hjälpsida

Det här avsnittet är också mycket kort: rk.plugin.skeleton() kan inte skriva en hel hjälpsida med den information den har. Men den kan söka igenom XML-dokumentet efter element som troligen förtjänar att omnämnas på hjälpsidan, och automatiskt skapa en mall för vårt insticksprogram. Allt vi måste göra efteråt är att skriva några rader för varje sektion som listas.

Tips

Funktionen rk.rkh.scan() kan också söka igenom befintliga XML-filer för att skapa en mall för hjälpfilen.

Generera insticksprogrammets filer

Nu kommer det sista steget, då vi lämnar över alla genererade objekt till rk.plugin.skeleton():

plugin.dir <- rk.plugin.skeleton("t-Test",
        xml=list(
                dialog=full.dialog,
                wizard=full.wizard),
        js=list(
                results.header="Two Variable t-Test",
                calculate=JS.calc,
                printout=JS.print),
        pluginmap=list(
                name="Two Variable t-Test",
                hierarchy=list("analysis", "means", "t-Test")),
        load=TRUE,
        edit=TRUE,
        show=TRUE)
                        

Filerna skapa normalt i en tillfällig katalog. De tre sista alternativen är inte nödvändiga, men mycket praktiska: load=TRUE lägger automatiskt till det nya insticksprogrammet i RKWards inställning (eftersom det finns i en tillfällig katalog, och därför slutar existera när RKWard stängs, tas det automatiskt bort igen av RKWard vid nästa start), edit=TRUE öppnar alla skapade filer för redigering i RKWards editorflikar, och show=TRUE försöker att direkt starta insticksprogrammet, så att du kan undersöka hur det ser ut utan något klick. Du kan överväga att lägga till overwrite=TRUE om du tänker köra skriptet upprepade gånger (t.ex. efter kodändringar), eftersom normalt skrivs inga filer över.

Resultatobjektet plugin.dir innehåller sökvägen till katalogen där insticksprogrammet skapades. Det kan vara användbart i kombination med funktionen rk.build.package() för att bygga ett verkligt R-paket, för att dela insticksprogrammet med andra, t.ex. genom att skicka det till RKWard-utvecklingsgruppen för att läggas till i vårt insticksprogramarkiv.

Hela skriptet

För att rekapitulera allt det ovanstående, här är hela skriptet för att skapa det fungerande t-test exemplet. Som tillägg till koden som redan har förklarats, läser det också in paketet vid behov, och använder miljön local(), så att de skapade objekten inte hamnar i din nuvarande arbetsrymd (utom plugin.dir förstås):

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("using test hypothesis",
                options=list(
                        "Two-sided"=c(val="two.sided"),
                        "First is greater"=c(val="greater"),
                        "Second is greater"=c(val="less")
                )
        )
        check.paired <- rk.XML.cbox("Paired sample", 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("assume equal variances", value="1", un.value="0")
        conf.level <- rk.XML.spinbox("confidence level", min=0, max=1, initial=0.95)
        check.conf <- rk.XML.cbox("print confidence interval", val="1", chk=TRUE)
        conf.frame <- rk.XML.frame(conf.level, check.conf, rk.XML.stretch(), label="Confidence Interval")

        full.dialog <- rk.XML.dialog(
                label="Two Variable t-Test",
                rk.XML.tabbook(tabs=list("Basic settings"=basic.settings, "Options"=list(check.eqvar, conf.frame)))
        )

        full.wizard <- rk.XML.wizard(
                        label="Two Variable t-Test",
                        rk.XML.page(
                                rk.XML.text("As a first step, select the two variables you want to compare against
                                        each other. And specify, which one you theorize to be greater. Select two-sided,
                                        if your theory does not tell you, which variable is greater."),
                                rk.XML.copy(basic.settings)),
                        rk.XML.page(
                                rk.XML.text("Below are some advanced options. It's generally safe not to assume the
                                        variables have equal variances. An appropriate correction will be applied then.
                                        Choosing \"assume equal variances\" may increase test-strength, however."),
                                rk.XML.copy(check.eqvar),
                                rk.XML.text("Sometimes it's helpful to get an estimate of the confidence interval of
                                        the difference in means. Below you can specify whether one should be shown, and
                                        which confidence-level should be applied (95% corresponds to a 5% level of
                                        significance)."),
                                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("t-Test",
                xml=list(
                        dialog=full.dialog,
                        wizard=full.wizard),
                js=list(
                        results.header="Two Variable t-Test",
                        calculate=JS.calc,
                        printout=JS.print),
                pluginmap=list(
                        name="Two Variable t-Test",
                        hierarchy=list("analysis", "means", "t-Test")),
                load=TRUE,
                edit=TRUE,
                show=TRUE,
                overwrite=TRUE)
})