Практичний приклад

Щоб зрозуміти, що являє собою «безпосереднє написання додатка», вам слід ознайомитися із попередніми розділами. Ми ж зараз створимо повноцінний додаток t-перевірки ще раз. Цього разу лише за допомогою функцій R пакунка rkwarddev.

Підказка

Пакунок додасть нове діалогове вікно RKWard, доступ до якого можна буде отримати за допомогою пункту меню ФайлЕкспортуватиСтворити скрипт додатка RKWard. Як можна здогадатися з назви пункту, за його допомогою ви зможете створити каркас скрипту додатка для його подальшого редагування. Відповідне діалогове вікно створюється скриптом rkwarddev, який ви можете знайти у каталозі «demo» встановленого пакунка, де зберігаються і коди пакунка, як додатковий приклад. Ви також можете запустити цей скрипт за допомогою команди demo("skeleton_dialog")

Опис графічного інтерфейсу

Тут можна одразу помітити, що принципи роботи є суттєво іншими: замість безпосереднього написання коду XML вручну, ви починаєте не з визначення <document>, а безпосередньо з елементів додатка, які ви хочете бачити у діалоговому вікні. Ви можете пов’язати кожен елемент інтерфейсу, — поля для позначок, спадні меню, слоти для змінних або щось інше, — з окремими об’єктами R, а потім поєднати ці об’єкти із справжнім графічним інтерфейсом. У пакунку є функції для кожного теґу XML, якими можна скористатися для визначення графічного інтерфейсу додатка. Більшість з цих функцій мають ті самі назви, що і елементи графічного інтерфейсу, лише додається префікс rk.XML.*. Наприклад, щоб визначити <varselector> і два елементи <varslot> для змінних x і y прикладу з t-перевіркою, слід написати такий код:

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

Найцікавішою деталлю, ймовірно, є визначення source=variables: визначною можливістю пакунка є те, що усі функції здатні створювати ідентифікатори автоматично, отже, вам не треба буде вигадувати усі значення id і запам’ятовувати їх для використання у відповідному елементі додатка. Ви можете просто надати еталонні об’єкти R, оскільки усі функції, яким потрібен ідентифікатор якогось іншого елемента, також можуть прочитати їх з цих об’єктів. Функція rk.XML.varselector() має особливість: зазвичай, у ній немає специфічних елементів, на основі яких можна створити ідентифікатор (це можливо, лише якщо вами вказано мітку), тому доводиться встановлювати назву ідентифікатора. А ось rk.XML.varslot() не потрібні аргументи id.name, достатньо написати так:

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)
                

Досі, для створення коду ми встановлювали усі ідентифікатори вручну. Але оскільки пакунок має полегшити наше життя, віднині ми більше цього не робитимемо.

Підказка

rkwarddev може автоматично виконувати значний обсяг роботи зі створення ваших додатків. Втім, іноді не варто використовувати усю його можливу потужність. Якщо вашою метою є написання коду, який не лише працюватиме, але і буде простим у читанні і порівнянні для інших програмістів, вам варто завжди створювати зручні для розуміння ідентифікатори за допомогою id.name. Іменування ваших об’єктів R ідентично до цих ідентифікаторів також допоможе у тому, щоб зробити код скрипту зрозумілішим.

Якщо ви хочете побачити, як виглядатиме код XML визначеного елемента, якщо ви експортуєте його до файла, ви можете просто викликати об’єкт за його назвою. Отже, якщо ви тепер виконаєте виклик «var.x» у вашому сеансі R, ви маєте побачити щось таке:

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

Деякі теґи мають сенс лише у контексті інших теґів. Тому, наприклад, ви не знайдете функцій для теґу <option> tag. Замість цього, у списках варіантів і спадних списках пункти визначаються за допомогою <option> як іменовані списки, де назви відповідають міткам, які буде показано у діалоговому вікні, а значення є іменованим вектором з двох записів: val для значення пункту та булевого значення chk, яке визначає, чи буде пункт типово позначено.

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

Результат виглядає десь так:

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

На вкладці «Basic settings» не вистачає лише елемента поля для позначки для парних вибірок та упорядковування елементів за рядками і стовпчиками:

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() є рідкісним виключенням, коли назва функції не містить повної назви теґу. Так зроблено, щоб трохи заощадити час на набиранні цього широковживаного елемента. Ось, що тепер міститиме basic.settings:

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

У подібний же спосіб, наступні рядки створять об’єкти R для елементів вкладки «Options», впровадивши функції для полів лічильника, рамок та розтягування:

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

Тепер нам залишилося лише об’єднати об’єкти у книгу з вкладками (tabbook), тобто додати таке у розділ dialog:

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

Ми також можемо створити розділ майстра з його двома сторінками, використовуючи ті самі об’єкти, їхні ідентифікатори буде видобути з теґів <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 is 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 is 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)))
                

Ну ось і все з графічним інтерфейсом. Загальний документ буде зібрано у кінці за допомогою rk.plugin.skeleton().

Код JavaScript

Досі, використання пакунка rkwarddev здавалося не дуже корисним. Давайте змінимо цю точку зору зараз же.

Перш за все, подібно до того, як ми не маємо дбати про ідентифікатори елементів, коли визначаємо компонування графічного інтерфейсу, нам не потрібно дбати про назви змінних JavaScript на наступному кроці. Якщо вам потрібні додаткові важелі керування, ви можете написати звичайний код JavaScript і вставити його до автоматично створеного файла. Але, ймовірно, найефективнішим способом все ж є використання rkwarddev.

Найпомітнішим є те, що вам не потрібно буде визначати жодну зі змінних власноруч, оскільки функція rk.plugin.skeleton() виконає сканування усього вашого коду XML і автоматично визначить усі потрібні вам змінні. Наприклад, вам не потрібно включати поле для позначки, якщо ви не використовуєте його значення або не встановлюєте його пізніше. Отже, ви зможете негайно розпочати написання дійсного коду JS для створення команд R.

Підказка

Функція rk.JS.scan() також може шукати змінні у наявних файлах XML.

У пакунку є декілька функцій для конструктів коду JS code, які часто використовуються у додатках RKWard, зокрема функція echo() та умови if() {...} else {...}. Між кодом JS і R є певні відмінності. Наприклад, у функції paste() R для з’єднання символьних рядків використовується кома, а у функції echo() JS слід з цією ж метою використовувати «+»; крім того рядки у JS слід завершувати крапкою з комою. Якщо ви використовуватимете функції R, ви зможете майже забути про ці відмінності і писати просто код мовою R.

Ці функції можуть отримувати різні класи вхідних об’єктів: звичайні текстові об’єкти, об’єкти R із кодом XML, як у наведеному вище прикладі, або результати виконання якихось інших функцій JS у пакунку. Наприкінці завжди слід викликати rk.paste.JS(), функцію, яка працює подібно до paste(), але, залежно від вхідних об’єктів, замінює їх на ідентифікатори XML, назви змінних JavaScript або навіть блоки коду JavaScript.

Для прикладу з t-перевіркою нам знадобляться два об’єкти JS: один для обчислення результатів і один для виведення цих результатів у функції 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)
                

Як можна бачити, у rkwarddev передбачено реалізацію мовою R функції echo(). Ця функція повертає точно один рядок символів із коректною версією самої функції мовою JS. Також можна помітити, що усі об’єкти R тут є раніше створеними нами об’єктами. Їх буде автоматично замінено відповідними назвами змінних, отже, ніяких проблем із ускладненням розуміння коду бути не повинно. Якщо вам потрібна буде така заміна, ви можете скористатися функцією id(), яка також повертає точно один рядок із символів на основі усіх переданих їй об’єктів (можна сказати, що вона поводить себе подібно до paste() із дуже специфічним підставлянням об’єктів).

Функція js() є обгорткою, яка надає вам змогу використовувати умови R if(){...} else {...} у звичний спосіб. Такі інструкції буде безпосередньо трансльовано у код JS. Функція також зберігає деякі оператори, зокрема <, >= або ||, отже ви зможете виконувати логічне порівняння ваших об’єктів R без потреби у взятті їх у лапки. Погляньмо на отриманий об’єкт «JS.calc», у якому тепер зберігається рядок з таким вмістом:

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

Примітка

Крім того, для умов if(), які вкладено до js(), ви можете скористатися функцією ite(), яка працює подібно до ifelse() у R. Втім, умови, побудовані за допомогою ite(), зазвичай важче читати, тому варто використовувати замість неї js() усюди, де це можливо.

Карта додатка

Цей розділ буде дуже коротким: нам не потрібно взагалі писати .pluginmap, оскільки ці дані може бути автоматично створено за допомогою rk.plugin.skeleton(). Ієрархію меню можна визначити за допомогою параметра pluginmap:

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

Довідкова сторінка

Цей розділ теж дуже короткий: rk.plugin.skeleton() не зможе повністю написати довідку на основі отриманої інформації. Але ця функція може виконати сканування документа XML і виявити елементи, які, ймовірно, потребують належного документування. На основі списку таких елементів буде автоматично створено шаблон сторінки довідки для нашого додатка. Нам достатньо буде дописати декілька пояснювальних рядків для кожного із розділів цієї сторінки.

Підказка

Функція rk.rkh.scan() також може шукати дані у наявних файлах XML для створення каркасу файла довідки.

Створення файлів додатка

Нарешті, останній крок, на якому ми передамо усі створені об’єкти 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)
                        

Типово, файли буде створено у тимчасовому каталозі. Останні три параметри не є обов’язковими, але значно спрощують роботу: load=TRUE автоматично додасть новий додаток до налаштувань RKWard (оскільки це тимчасовий каталог і, отже, його буде спорожнено після закриття RKWard, запис додатка буде автоматично вилучено RKWard під час наступного запуску програми), edit=TRUE відкриє усі створені файли для редагування у вкладках редактора RKWard, а show=TRUE спробує безпосередньо запустити додаток, щоб ви могли ознайомитися з його виглядом навіть не клацаючи кнопкою миші. Ви також можете скористатися параметром overwrite=TRUE, якщо ви запускаєте ваш скрипт повторно (наприклад, після змін у коді), оскільки типово після таких змін файли не перезаписуватимуться.

Об’єкт-результат «plugin.dir» містить шлях до каталогу, у якому було створено додаток. Він буде корисним у поєднанні з функцією rk.build.package(), для створення самого пакунка R, яким ви зможете поділитися з іншими користувачами, наприклад, надіслати його команді розробників RKWard для додавання до нашого сховища додатків.

Скрипт повністю

Підсумовуючи усе сказане вище, наведемо скрипт для створення працездатного прикладу t-перевірки повністю. Окрім вже описаного коду, у скрипті містяться команди завантаження пакунка за запитом та використання середовища local(), щоб усі створені об’єкти не потрапили до вашого поточного робочого простору (окрім «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("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 is 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 is 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)
})