Журнал LinuxFormat - перейти на главную

LXF129:R

Материал из Linuxformat
Перейти к: навигация, поиск
R Создайте для своих статистических приложений удобный графический интерфейс

Содержание

R: GUI на примере

Мы уже описывали доступные для R графические интерфейсы, а сегодня Сергей Петров и Евгений Балдин покажут, как создавать их самостоятельно.

Отдавать команды компьютеру можно исключительно из командной строки R. И наоборот, R Commander по зволяет практически всё делать мышью. Очевидно, что часто требуется нечто среднее между этими двумя «мирами». Назовём этот средний путь интерфейсом «наведи и щелкни». Он особенно эффективен, когда мы визуализируем обрабатываемые данные.

Анализ данных часто сводится к написанию своей специализированной программы. Если данных действительно много, то программирование становится для исследователя неизбежной необходимостью. Невозможно предусмотреть всё многообразие данных, пока их не исследуешь, поэтому процесс анализа идёт параллельно с написанием всё более и более интеллектуальной системы для его проведения.

Динамический графический интерфейс программы анализа должен быть прост, незатейлив и не отвлекать от самого главного, а именно – от собственно анализа данных. Он должен быть тесно интегрирован с возможностями командной строки. Этим требованиям в полной мере отвечает пакет rpanel.

rpanel

Rpanel – это крайне простая и одновременно высокоуровневая надстройка над Tcl/Tk-расширением среды R. Наравне с Perl и Python, Tcl является «классическим» скриптовым языком высокого уровня, который обычно используют совместно с кроссплатформенной библиотекой графических элементов Tk. Многие языки программирования, и R в том числе, применяют Tk для построения простых графических интерфейсов.

Если пакет rpanel отсутствует в системе, то скачать и установить его можно с помощью команды

> install.packages(“rpanel”)

После этого загружаем саму библиотеку:

> library(rpanel)

Логика использования пакета довольно проста:

  1. С помощью функции rp.control нужно создать объект panel и объявить в нём нужные нам переменные;
  2. Далее необходимо «населить» объект panel различными элементами графического интерфейса (ползунками, кнопками, картинками и т. д.) и связанными с ними переменными;
  3. В самом конце требуется определить функцию, которая будет выполняться в ответ на взаимодействие пользователя с графическим интерфейсом.

Разберём простейший пример, в котором функция выводит на экран одно из двух: или гистограмму полученных данных, или боксплот («ящик-с-усами»), в зависимости от значения, выбранного в элементе rp.radiogroup (радиокнопке).


Объявим объект panel и данные, которые будем отображать:

> panel <- rp.control(x=rnorm(50))

Здесь мы «исследуем» x, вектор данных, имеющих гауссовское распределение. Далее, поместим в panel переключатель rp.radiogroup и свяжем его с переменной plot.type и функцией hist.or.boxp:

> rp.radiogroup(panel, # сам объект
+ plot.type, # переменная
+ c(“histogram”, “boxplot”), # значения переключателя
+ title=”Plot type”, # название переключателя
+ action = hist.or.boxp) # пользовательская функция

Наконец, объявим функцию, которая будет выполнятся в ответ на взаимодействие пользователя с переключателем:

> hist.or.boxp <- function(panel) {
+ if (panel$plot.type == “histogram”)
+ hist(panel$x)
+ else
+ boxplot(panel$x)
+ panel
+ }

Доступ к переменным внутри объекта panel производится путём обращения вида panel$имя_переменной.

Теперь всё готово. У нас есть готовая панель с переключателем, который в зависимости от выбора показывает либо гистограмму, либо боксплот.

При желании можно «вмонтировать» график прямо в панель как один из элементов графического интерфейса. Для этого придётся воспользоваться командами rp.tkrplot и rp.tkrreplot из пакета tkrplot. Перед их добавлением необходимо убедиться, что установлены системные пакеты tk-dev и tcl-dev:

=> sudo aptitude install tk-dev tcl-dev


Затем, как обычно, устанавливаем и загружаем сам пакет:

> install.packages(“tkrplot”)
> library(tkrplot)

Открываем панель rpplot и объявляем переменную h:

> rpplot <- rp.control(title = “Демонстрация rp.tkrplot”, + h = 1)

Помещаем на панели rpplot график tkrp:

> rp.tkrplot(rpplot,
+ tkrp,
+ function(panel) plot(1:20,(1:20)^panel$h))

В зависимости от значения параметра h на графике отображается соответствующая степенная функция. Далее, надо определить пользовательскую функцию для перерисовки графика:

> redraw <- function(panel) {
+ rp.tkrreplot(panel, tkrp)
+ }

Для динамического изменения параметра h добавим в панель rpplot ползунок и свяжем его с пользовательской функцией redraw:

# Помещаем на панель ползунок
> rp.slider(rpplot, # панель
+ h, # переменная ползунка
+ action = redraw, # пользовательская функция
+ from = 0.05, to = 2.00, # параметры ползунка
+ resolution = 0.05,
+ title=”степень”) # название ползунка

Tcl/Tk

Если возможностей rpanel не хватает, то можно спуститься уровнем ниже и обратиться напрямую к Tcl/Tk. Нужный пакет так и называется – tcltk.

Инструментарий Tk предоставляет богатый набор элементов пользовательского интерфейса. К ним относятся окна редактирования текста, полосы прокрутки, поля ввода, кнопки, метки, списки и рисунки. Всё это можно комбинировать и получать сложный графический интерфейс.

Простейший пример программы «Здравствуй, мир!» с использованием Tk выглядит так:

> library(tcltk) # Загружаем пакет Tcl/Tk
> tt <- tktoplevel() # Объявляем контейнер
# Объявляем метку
> lbl <- tklabel(tt,text=”Здравствуй, мир!”)
> tkpack(lbl) # Размещаем метку в контейнере

В результате выполнения этого кода на экран будет выведено окно с популярной надписью «Здравствуй, мир!». Картинку этого шедевра программистского искусства мы не предоставляем, так как она достаточно банальна. Выполните пример самостоятельно – он не сложен.

Схема построения графического интерфейса с Tk всегда одинакова: объявляется контейнер, в котором с помощью менеджера геометрии (tkpack) размещаются дочерние виджеты.

В контейнер можно добавить сколько угодно графических элементов. Процесс построения сложного приложения заключается в комбинировании элементарных блоков. Например, добавим в наше окно кнопку «Выполнить!»:

> but <- tkbutton(tt, text=”Выполнить!”)
> tkpack(but)

На кнопку уже можно нажимать, хотя никаких действий с ней пока не связано.

Окно контейнера, в котором выведен наш пример, называется «1» (значение по умолчанию). Изменим это:
> tktitle(tt) <- “Моё окно”

В Tcl/Tk доступны три менеджера геометрии. Простейший из них, placer, почти никогда не применяется. Более популярны оставшиеся два: packer и grid. У packer есть два параметра: «порядок» и «направление».

Если попробовать изменить мышью размеры окна предыдущего примера, вы увидите, что виджеты располагаются по центру окна у его верхней границы. Если начать уменьшать размер окна по вертикали, сначала сожмётся и исчезнет кнопка «Выполнить!», а потом начнёт изменяться надпись «Здравствуй, мир!». Попытки ручной коррекции размеров окна приводят к ещё одному эффекту: теперь оно не будет автоматически корректировать свои размеры при размещении в нем новых элементов. Восстановить автоматическую подгонку размеров окна можно, выполнив команду:

> tkwm.geometry(tt,””)

Элементы графического интерфейса можно привязать к любой из сторон основного окна. Например, вот так:


> tkdestroy(tt) # Убираем предыдущий пример
> tt <- tktoplevel()
# Английские названия используются и как имена кнопок,
# и как параметры
> edge <- c(“top”,”right”,”bottom”,”left”)
> for ( i in 1:4 ) # Четыре кнопки
+ tkpack(buttonsi, side=edge[i],
+ fill=”both”)

При этом виджет занимает достаточную для своего расположения полосу, параллельную стороне привязки. Указанный параметр fill заставляет элемент занять полосу целиком. Аналогично, если указать параметр expand=TRUE, то полоса захватит всё свободное место.

Когда размещаемый графический элемент интерфейса не занимает полосу целиком, его можно «заякорить», подав «морские» команды вида «n» (nord – север или верх основного окна) или «sw» (зюйд-вест или внизу слева).

Если нам, к примеру, понадобится разместить в основном окне область ввода текста с полосой прокрутки, то мы:

  1. Добавляем полосе прокрутки свойство fill=«y» (растягиваться по вертикали);
  2. Добавляем области ввода текста fill=«both» и expand=TRUE, что бы она заняла все свободное пространство;
  3. Первой в основное окно помещаем полосу прокрутки, а потом область ввода текста, чтобы полоса прокрутки не сокращалась при изменении размеров окна пользователем.

Если же вы задумались, как объединить ряд кнопок и область ввода текста, то нет ничего проще: надо всего лишь включить кнопки во фрейм. Каждый фрейм – это контейнер со своим собственным менеджером геометрии. Гибкость в размещении виджетов этими средствами достигается весьма изрядная. Однако иногда такой способ требует от пользователя изощрённых подходов. Например, достаточно трудно выровнять ряды кнопок одновременно и по вертикали, и по горизонтали. В случае, если вас замучила именно эта проблема, на помощь придет менеджер геометрии grid. Он размещает графические элементы интерфейса стройными рядами и колоннами.

Следующий пример рекомендуется выполнить построчно, наблюдая за изменением вывода в основном окне:


> t2 <- tktoplevel()
> heading <- tklabel(t2, text=”Анкета”)
> l.name <- tklabel(t2, text=”Ф.И.О.”)
> l.age <- tklabel(t2, text=”Возраст”)
> e.name <- tkentry(t2, width=30)
> e.age <- tkentry(t2, width=3)
> tkgrid(heading, columnspan=2)
> tkgrid(l.name, e.name)
> tkgrid(l.age, e.age)
> tkgrid.configure(e.name, e.age, sticky=”w”)
> tkgrid.configure(l.name, l.age, sticky=”e”)

Обратные вызовы

С таким достаточно приличным и доставшимся «малой кровью» графическим интерфейсом уже хочется общаться. А именно: получать от него данные и отправлять в него результаты обработки. Для этого есть два основных способа: «управляющие переменные» и «обратные вызовы» [callbacks].

Для создания управляющей переменной Tcl служит функция tclVar(), возвращающая объект класса tclVar. На стороне Tcl созданные переменные будут названы автоматически, и в обычной ситуации беспокоится о задании какого-то специального имени необходимости нет. Получить значение созданной переменной Tcl можно с помощью функции tclvalue().

Чтобы поместить в созданную переменную содержимое, не столкнувшись при этом с проблемами кавычек и других символов, имеющих в строках Tcl служебное значение, следует задействовав функцию tclObj():

# Создаем в R переменную foo
> foo <- tclVar()
# Заполняем foo символами
> tclObj(foo) <- c(pi,exp(1))
# Выводим значение foo
> tclvalue(foo)
[1] “3.141592653589793 2.718281828459045”
# Разбираем строку на символьные значения
> as.character(tclObj(foo))
[1] “3.141592653589793” “2.718281828459045”
# Разбираем строку на вещественные значения
> as.double(tclObj(foo))
[1] 3.141593 2.718282
# Чего в строке нет, того нет
> as.integer(tclObj(foo))
[1] NA NA
# Округляем до целых самостоятельно.
> round(as.double(tclObj(foo)))
[1] 3 3

Доступ через tclObj() экономит значительные усилия. Например, вот так выглядит стандартная подготовка списка названий месяцев:


# Имена месяцев в представлении R
> month.name
[1] “January” “February” “March” “April”
[5] “May” “June” “July” “August”
[9] “September” “October” “November” “December”
# Теперь строка, пригодная для экспорта в Tcl
> paste(month.name, collapse=” “)
[1] “January February March April May June July
+ August September October November December”

Она заменяется достаточно простым выражением:

> mylist <- tclVar()
> tclObj(mylist) <- month.name
> tclObj(mylist)
<Tcl> January February March April May June July
+ August September October November December

Имея на руках переменную Tcl, общаться с созданным графическим интерфейсом очень просто. Построим форму со списком названий месяцев:

> tkpack(lb <- tklistbox(tt <- tktoplevel(), 
+ listvariable = mylist))

В любой момент можно узнать, какой месяц был выбран пользователем. При этом надо помнить, что индексы в Tcl начинаются с 0:

> as.character(tclObj(mylist))[as.integer(tkcurselection(lb))+1]
[1] “July”

Значения, возвращаемые из Tcl-вызовов, также являются объектами tclObj. Например, мы можем включить в окне с нашим построенным списком месяцев режим множественного выбора:

> tkconfigure(lb, selectmode=”multiple”)

Выбрав нужные позиции списка, мы легко получаем их в виде номеров строк:

> as.integer(tkcurselection(lb))
[1] 1 3 5

Теперь попробуем привязать к кнопке какое-либо действие, например, пусть она сообщает о своем нажатии в R, и меняет надпись:

# Создаём основное окно
> t <- tktoplevel()
# Создаем кнопку
> b <- tkbutton(t, text = “Не жми на меня!”)
# Размещаем её в основном окне
> tkpack(b)
# Объявляем функцию кнопки
> change.text <- function() {
+ cat(“ОГО!\n”)
+ tkconfigure(b, text = “Говорю тебе: не жми на меня!”)
+ }
# Прикрепляем функцию к кнопке
> tkconfigure(b, command = change.text)

Теперь кнопка реагирует на нажатие: при каждом щелчке в консоли появляются восклицания «ОГО!» и меняется метка. Это простой пример – попробуйте его в динамике.

Кнопку для краткости можно сразу объявить с нужной нам функцией:

> b <- tkbutton(t, text=”Не жми на меня!”,
+ command=expression(cat(“ОГО!\n”),
+ tkconfigure(b, text = “Говорю тебе: не жми на меня!”)))

Немного текста

Каждый из элементов графического интерфейса Tk имеет массу полезных применений. Например, окно для ввода текста представляет собой практически готовый текстовый редактор. В нём можно вводить и править текст, перемещаться по нему клавишами курсора, выделять куски текста для копирования или вставки.

Чтобы получить текст из окна, надо выполнить команду

> tkget(txt, “0.0”, “end”)

Поскольку Tсl имеет дело со строками, то и передаём мы только строки. «Первая строка, первый символ» – это 0.0, а end – это, соответственно, конец текста. Если мы захотим получить текст в R разбитым на строки так же, как в текстовом окне, то нам надо конвертировать строку Tcl в вектор:

> strsplit(tkget(txt, “0.0”, “end”),”\n”)

Похожим способом можно получить выделение текста в окне:

> X <- tkget(txt, “sel.first”, “sel.last”)

Здесь нам уже не обойтись без обработки ошибок, а то вдруг выделение текста не было сделано? Проверить это просто, путём сравнения с пустой строкой:

> tktag.ranges(txt, “sel”) != “”

Можно получить информацию и о нажатии кнопки мыши, например, узнать координаты указателя и оставить текстовый курсор на месте:

> tkbind(txt, “<Control-Button-1>”,
+ expression(function(x,y) cat(x,y,”\n”),
+ break))

Чтобы дописать какую-либо строку в конец текста, расположенного в окне, можно использовать такую конструкцию:

> tkinsert(txt, “end”, # Добавляем в конец текста
string) # Строка, которую добавляем

Естественно, если мы хотим передать в Tcl несколько строк сразу, понадобится объединить их, например, таким способом:

> tkinsert(txt, “end”,
paste(deparse(ls), collapse=”\n”))

Если нам надо переместить текстовый курсор в определенное место окна, то мы можем воспользоваться сочетанием:

# Переместить курсор в начало первой строки
> tkmark.set(txt, “insert”, “0.0”)
# Сделать видимой область с курсором
> tksee(txt, “insert”)

Мы также можем контролировать, куда будет смещаться вставляемый текст, если ему не хватает места. По умолчанию он вставляется перед выделением, но это легко изменить:

> tkmark.gravity(txt, “insert”, “left”)

Ваше меню

Если наше приложение достаточно сложное, то ему, безусловно, понадобится меню. Меню в Tk тоже есть – это отдельный элемент. Можно использовать всплывающее меню, но чаще его размещают в основном окне в качестве полоски или кнопки «Пуск», или как меню, вложенное внутри главного меню приложения.

Наиболее практично создавать меню по шагам. Сначала нужно объявить его командой tkmenu, затем наполнить элементами с помощью tkadd. Это позволит легко воспользоваться многочисленными параметрами команд создания элементов меню.

Существует множество разновидностей элементов меню. Это эквивалентные уже знакомым нам кнопкам command entry. Вложенное меню получают, вставляя Cascade entries.Checkbutton и radiobutton позволяют реализовывать различные варианты выбора параметров. Есть и доступ к элементам, организующим содержимое меню: это separators, отделяющие элементы меню визуально друг от друга, и tear-offs, позволяющие откреплять меню от родительского и делать его плавающим.

Чтобы не быть голословными, создадим кнопку меню с тремя radiobutton:

# Объявляем переменную изменяемую из меню
> color <- tclVar
#и её значение по умолчанию
> color<-”красный”
> tt <- tktoplevel()
# Размещаем кнопку меню
> tkpack(mb <- tkmenubutton(tt, text=”Цвет”))
# Объявляем вложенное меню
> m <- tkmenu(mb)
# Присоединяем вложенное меню к кнопке
> tkconfigure(mb,menu=m)
# Создаём элементы меню
> for ( i in c(“красный”, “синий”, “зелёный”))
tkadd(m, “radio”, label=i, variable=color, value=i)

Узнать, какой из вариантов меню был выбран пользователем, можно с помощью функции

> tclvalue(color)
[1] “красный”

Ваше слово

Теперь мы во всеоружии, то есть можем не просто наслаждаться высокопродуктивной обработкой данных в R сами, но и сделать свои наработки доступными для не столь искушённых в R окружающих.

К сожалению, нет в мире совершенства. Вдруг нам понадобится сделать наше предложение доступным через корпоративный web-сервер? Или нам не нравится внешний вид Tk? Если вы еще не привыкли к тому, что в R, как в Греции, есть всё, то вот оно – решение: gWidgets.

Вместо того, чтобы опускаться вглубь конкретной библиотеки графического интерфейса, можно просто сделать свое приложение библиотеко-независимым. Но это тема для отдельного рассказа.

Персональные инструменты
купить
подписаться
Яндекс.Метрика