27 мая 2014 г.

Байесовы сети в R: пакет bnlearn

Описание модели

На протяжении семестра каждый студент проходит две текущих аттестации (получая от 0 до 45 баллов на каждой) и одну итоговую (получая от 1 до 5 баллов). Рассмотрим взаимодействие с байесовой сетью на примере анализа этих результатов.

Сведения о результатах промежуточной и итоговой аттестации студентов представим в виде объекта data.frame:

> head(rating)
        subj type id rate
1 Теория игр    1  1   35
2 Теория игр    1  2   25
3 Теория игр    1  3   35
4 Теория игр    1  4   25
5 Теория игр    1  5   10
6 Теория игр    1  6   35

Где:
subj — наименование дисциплины;
type — вид аттестации (1-я, 2-я и 3-я, итоговая);
id — идентификатор студента;
rate — соответствующая оценка.

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

  • вектора оценок студентов по паросочетаниям предмет — номер аттестации;
  • вектора оценок по предметам в паросочетаниях студент — номер аттестации.

В первом случае байесова сеть накапливает знания о связях между результатами аттестации в рамках одного предмета, во втором — о связях между результатами аттестации одного студента по всем предметам.

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

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

Обучение байесовой сети

Обратите внимание, представление оценок студентов в интервальной шкале методически ошибочно, пример ниже иллюстрирует исключительно работу с пакетом bnlearn и не дает возможности делать содержательные выводы.

Сформируем связи переменных в байесовой сети на основании экспертных знаний:

> net <- model2network("[A1][A2|A1][A3|A1:A2]")

Структура связей этой сети изображена на рисунке.


Структура зависимостей переменных в байесовой сети.

Сформируем набор переменных с результатами аттестаций по дисциплине «Теория игр»:

> a1 <- rating$rate[rating$subj == "Теория игр" & rating$type == 1]
> a2 <- rating$rate[rating$subj == "Теория игр" & rating$type == 2]
> a3 <- rating$rate[rating$subj == "Теория игр" & rating$type == 3]

В обучении байесовой сети пакет bnlearn может использовать объект типа data.frame, содержащий таблицу переменных типа num. Тип переменных a1, a2 и a3int, что необходимо учитывать при обучении сети:

> str(a1)
 int [1:8] 35 25 35 25 10 35 25 30
> str(a2)
 int [1:8] 35 25 35 25 20 25 25 25
> str(a3)
 int [1:8] 5 3 5 3 3 5 4 4
> rate <- data.frame(A1 = as.numeric(a1), A2 = as.numeric(a2), A3 = as.numeric(a3))
> net.rate <- bn.fit(net, rate)

Если эту особенность проигнорировать, произойдет следующее:

> rate <- data.frame(A1 = a1, A2 = a2, A3 = a3)
> net.rate <- bn.fit(net, rate)
Ошибка в check.data(data) : 
  variables must be either all real numbers or all factors.

Объект net.rate хранит обученную байесову сеть. Используя его, мы можем получать ответы на вероятностные вопросы. Естественно, эти ответы зависят от обучающей выборки.

Прогнозирование на основе обученной байесовой сети

Ниже приведены примеры построения прогнозов на основе байесовой сети net.rate.

  1. Какова вероятность получить положительную оценку для студента, если результаты его аттестаций по дисциплине пока неизвестны?
  2. > cpquery(net.rate, (A3 > 2.5), TRUE)
    [1] 0.9393
  3. Какова вероятность для студента получить «удовлетворительно», «хорошо» или «отлично» при тех же условиях?
  4. > cpquery(net.rate, (A3 > 2.5 & A3 < 3.5), TRUE)
    [1] 0.2479667
    > cpquery(net.rate, (A3 > 3.5 & A3 < 4.5), TRUE)
    [1] 0.3839
    > cpquery(net.rate, (A3 > 4.5), TRUE)
    [1] 0.3064333
    > 0.2479667 + 0.3839 + 0.3064333
    [1] 0.9383

    Сумма вероятностей сходится с предыдущим ответом с точностью до двух знаков после запятой. Отклонение объясняется спецификой модели расчетов (метод по умолчанию: logic sampling) — чем сложнее структура сети, тем больше может быть расхождение даже для идентичных запросов, сделанных друг за другом.

  5. Если студент получил больше 40 баллов за каждую из текущих аттестаций, каковы его шансы не получить на экзамене «отлично»?
  6. > cpquery(net.rate, (A3 < 4.5), (A1 > 40 & A2 > 40))
    [1] 0.015625
  7. Если студент получил меньше 20 баллов за каждую из текущих аттестаций, каковы его шансы получить на экзамене положительную оценку?
  8. > cpquery(net.rate, (A3 > 2.5), (A1 < 20 & A2 < 20))
    [1] 0.5664931

    Обратите внимание, шансы положительной оценки существенно меньше, чем в случае, когда о результатах аттестаций ничего не известно.

  9. А если студент получил меньше 20 баллов хотя бы за одну из текущих аттестаций?
  10. > cpquery(net.rate, (A3 > 2.5), (A1 < 20 | A2 < 20))
    [1] 0.7522488

    Шансы на положительную оценку в этом случае выше, чем в предыдущем.

Анализ причинности на основе обученной байесовой сети

Построение прогнозов — не единственный вариант использования обученной байесовой сети. Ниже приведены примеры диагностирования, построенные на ее основе.

  1. Студент получил на экзамене «неудовлетворительно». Какова вероятность того, что хотя бы на одной промежуточной аттестации он получил меньше 20 баллов?
  2. > cpquery(net.rate, (A1 < 20 | A2 < 20), (A3 > 1.5 & A3 < 2.5))
    [1] 0.8404692

  3. Тот же вопрос для студента, получившего на экзамене «хорошо».
  4. > cpquery(net.rate, (A1 < 20 | A2 < 20), (A3 > 3.5 & A3 < 4.5))
    [1] 0.1283003

  5. Студент получил на экзамене «удовлетворительно». Что вероятнее: у него есть аттестация хуже 30 баллов или лучше 30 баллов?
  6. > cpquery(net.rate, (A1 < 30 | A2 < 30), (A3 > 2.5 & A3 < 3.5))
    [1] 0.9836088
    > cpquery(net.rate, (A1 >= 30 | A2 >= 30), (A3 > 2.5 & A3 < 3.5))
    [1] 0.1065574

    Вероятность первого ответа — существенно выше.

Наконец, приведем варианты межпричинного анализа на основе байесовой сети.

  1. Результат первой аттестации студента ниже 30 баллов. Какова вероятность того, что он получит на второй аттестации результат лучше, чем на первой?
  2. > cpquery(net.rate, (A2 > A1), (A1 < 30))
    [1] 0.6594571
  3. Один студент во втором семестре учился лучше, чем в первом, другой — хуже. У кого вероятность результата второй аттестации выше 30 баллов больше?
  4. > cpquery(net.rate, (A2 > A1), (A2 > 30))
    [1] 0.3975462
    > cpquery(net.rate, (A2 < A1), (A2 > 30))
    [1] 0.6032461

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

Заключение

Нами рассмотрена работа с байесовой сетью, обученной на вещественных переменных. В случае с оценкой работы студентов лучше использовать переменные — факторы. Более того, пакет bnlearn позволяет использовать упорядоченные факторы.

15 апр. 2014 г.

R: трассировка маршрута

Сформируем кратчайший круговой маршрут, являющийся приближенным решением задачи коммивояжера для населённых пунктов Можгинского района.

Ранее мы вычислили для них матрицу расстояний (пояснения)…

> require("igraph")
> mozhga <- read.csv("mozhga.csv", sep = ";", dec = ",")
> mozhga.graph <- graph.data.frame(mozhga, directed = FALSE)  # Формируем граф,
> mozhga.dist <- shortest.paths(mozhga.graph)  # вычисляем матрицу расстояний
> d <- mozhga.dist[grep("_", colnames(mozhga.dist), invert = T), 
+                  grep("_", rownames(mozhga.dist), invert = T)]  # и избавляемся
> # от лишних узлов (предварены символом "_").

… и определили порядок их обхода (пояснения).

> require("TSP")
> mozhga.tsp <- TSP(d)
> mozhga.tour <- solve_TSP(mozhga.tsp, method = "2-opt")
> mozhga.tour.labels <- labels(mozhga.tour)

Элемент списка $vpath, возвращаемого функцией get.shortest.paths {igraph}, содержит номера вершин, составляющих кратчайший маршрут между двумя из них, заданными в качестве параметров.

Функция get.vertex.attribute {igraph} возвращает имя для данного номера вершины.

Код, формирующий список имен населённых пунктов в порядке их участия в маршруте:

> way <- sapply(1:(length(mozhga.tour.labels) - 1), function(x) {
+     get.shortest.paths(mozhga.graph,
+                        mozhga.tour.labels[x],
+                        mozhga.tour.labels[x + 1])$vpath[[1]][-1]
+ }) # Собираем номера вершин графа, в порядке движения по маршруту.
> way <- unlist(way)
> fullway <- c(mozhga.tour.labels[1], unlist(sapply(way, function(x) {
+     get.vertex.attribute(mozhga.graph, "name", index = x)
+ }))) # Преобразуем номера вершин в названия населённых пунктов и перекрёстков.
> head(fullway) # Выводим начало…
[1] "Ломеслуд"  "_1"        "_2"        "_3"        "Полянское" "_3"
> tail(fullway) # … и конец маршрута.
[1] "_2"       "_1"       "Ломеслуд" "Камышлы"  "Ломеслуд" "Кр. Яр"

Итоговый маршрут обхода (общая протяженность — 568.4 км) представлен на иллюстрации.

9 мар. 2014 г.

R: Формирование матрицы расстояний

Непосредственное измерение расстояний между населенными пунктами для заполнения матрицы расстояний — трудоёмкая задача. В случае развитой дорожной сети:

  • сложно определить кратчайший маршрут движения между пунктами;
  • велик объём работы (для \(n\) населенных пунктов число измерений составит \(n^2 - n \over 2 \));
  • сложно объединить матрицы расстояний нескольких районов.

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

Сформировать матрицу кратчайших расстояний между всеми парами вершин произвольного связного графа можно при помощи функции floyd.warshall.all.pairs.sp из пакета RBGL. В качестве единственного аргумента эта функция принимает объект класса graph, поддерживаемый одноименным пакетом. Оба этих пакета являются частью проекта Bioconductor, с особенностями их установки и использования можно ознакомиться на соответствующих страницах: RBGL и graph.

Виртуальный класс graph объединяет несколько подклассов, отличающихся способом описания графа. Класс graphAM хранит граф в виде матрицы связности его вершин, класс graphBAM — в виде таблицы с описанием его дуг.

Применим перечисленные инструменты на примере представления в виде графа дорожной сети Можгинского района Удмуртской республики — сформируем и сохраним в формате csv таблицу, первый и второй столбец которой соответствуют узлам дорожной сети (населенным пунктам и перекресткам), а третий столбец — протяженности соединяющих их участков дороги с твёрдым покрытием:

"from";"to";"weight"
"Кр. Яр";"Ломеслуд";3,5
"Камышлы";"Ломеслуд";1,75
"_1";"Ломеслуд";4,55
"_1";"_2";1,75
"_3";"_2";1,05
"_3";"Полянское";2,1
"_3";"Бол. Уча";1,4
"Пазял-Зюмья";"Бол. Уча";2,1
…
"Водзя";"Чежесть-Какси";2,8

На карте результат процесса выглядит так:

Всего — 111 дуг, 96 вершин (в т. ч. 21 перекрёсток и 4 населённых пункта не относящихся к Можгинскому району), примерно 3 человеко-часа работы.

Код R, формирующий матрицу расстояний между всеми населёнными пунктами Можгинского района представлен ниже:

> require("graph")
> require("RBGL")
> mozhga <- read.csv("mozhga.csv", sep=";", dec=",")
> mozhga.BAM <- graphBAM(mozhga) # Формируем граф экспериментального класса graphBAM,
> mozhga.AM <- as(mozhga.BAM, "graphAM") # преобразуем его в стабильный класс graphAM,
> mozhga.dist <- floyd.warshall.all.pairs.sp(mozhga.AM) # вычисляем матрицу расстояний
> d <- mozhga.dist[grep("_", colnames(mozhga.dist), invert=T), # и избавляемся от лишних узлов
+                  grep("_", rownames(mozhga.dist), invert=T)] # (предварены символом "_").

Объект d — искомая матрица расстояний между населенными пунктами Можгинского района.

UPD:

Вместо пакетов graph и RBGL удобнее воспользоваться пакетом igraph. В этом случае код R, формирующий матрицу расстояний, будет выглядеть следующим образом:

> require("igraph")
> mozhga.graph <- graph.data.frame(mozhga, directed=FALSE) # Формируем граф,
> mozhga.dist <- shortest.paths(mozhga.graph) # вычисляем матрицу расстояний
> d <- mozhga.dist[grep("_", colnames(mozhga.dist), invert=T), # и избавляемся от лишних узлов
+                  grep("_", rownames(mozhga.dist), invert=T)] # (предварены символом "_").

5 нояб. 2013 г.

Картограммы в R: используем OpenStreetMap в качестве фона

При помощи библиотеки OpenStreetMap, R-project может использовать растровые карты проекта OpenStreetMap.

Среди ее зависимостей находятся библиотеки rJava и rgdal. Про установку библиотеки rgdal я писал ранее. Установка библиотеки rJava также требует дополнительных действий — в ее зависимости входит Java JRE и Java JDK. Ubuntu 12.04 precise использует в качестве Java-окружения OpenJDK 6, так что для удовлетворения зависимостей rJava достаточно дополнительно установить пакет openjdk-6-jdk и познакомить с ним R:

$ sudo aptitude install openjdk-6-jdk
$ sudo R CMD javareconf

После такой подготовки системы, библиотека OpenStreetMap успешно устанавливается и мы можем переходить к её использованию. С тем, как получены объекты brks, part и udm.TIK можно познакомиться в предыдущих статьях.

> map <- openmap(c(59, 49), # Широта и долгота северо-западного угла карты.
+                           c(55, 56), # Широта и долгота юго-восточного угла карты.
+                           zoom=7,    # Масштаб карты, можно посмотреть в ее URL:
+ # http://www.openstreetmap.org/#map=7/57.035/53.553 (выделен красным).
+                           type="osm") # Тип скачиваемой карты (tile server). Есть и другие.
> colors <- rev(heat.colors(length(brks), alpha=1/3))
> udm.TIK.osm <- spTransform(udm.TIK, osm()) # Меняем проекцию объекта udm.TIK на используемую
# проектом OpenStreetMap.
> str(map) # Смотрим размеры карты в пикселах (выделены красным):
List of 2
 $ tiles:List of 1
  ..$ :List of 5
  .. ..$ colorData : chr [1:422878] "#F1EEE8" "#F1EEE8" "#F1EEE8" "#F1EEE8" ...
  .. ..$ bbox      :List of 2
  .. .. ..$ p1: num [1:2] 5454655 8180387
  .. .. ..$ p2: num [1:2] 6233891 7361866
  .. ..$ projection:Formal class 'CRS' [package "sp"] with 1 slots
  .. .. .. ..@ projargs: chr "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0
+x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"
  .. ..$ xres      : int 667
  .. ..$ yres      : int 634
  .. ..- attr(*, "class")= chr "osmtile"
 $ bbox :List of 2
  ..$ p1: num [1:2] 5454655 8180387
  ..$ p2: num [1:2] 6233891 7361866
 - attr(*, "zoom")= int 7
 - attr(*, "class")= chr "OpenStreetMap"
> png("ER-osm.png", width=634, height=667, units="px") # Рисуем. Размер может быть любым,
#                            карта будет отмасштабирована с сохранением отношения сторон.
> plot(map)
> plot(udm.TIK.osm, col=colors[findInterval(part, brks, all.inside=T)],
+ border="transparent", add=TRUE)
> legend("left", inset=0.1, legend=leglabs(brks, under="<", over=">"), fill=colors, bty="n")
> title(main="Доля голосов за ЕР\nв муниципальных районах\nи городских округах Удмуртии")
> dev.off()

Буду благодарен за указание способа узнать разрешение растровой карты автоматически.

3 нояб. 2013 г.

Решение задачи коммивояжера в R

Для решения задачи коммивояжера в R-project можно использовать пакет TSP:

> library("TSP")
# Заполним матрицу расстояний между городами…
> m <- matrix(c(0,   222, 652, 52,  447, 160, 445, 134, 380, 112, 536, 
+               222, 0,   594, 170, 565, 278, 223, 252, 322, 230, 654,
+               652, 594, 0,   600, 995, 708, 817, 682, 272, 660, 1084,
+               52,  170, 600, 0,   395, 108, 393, 82,  328, 60,  484,
+               447, 565, 995, 395, 0,   503, 340, 313, 723, 455, 519,
+               160, 278, 708, 108, 503, 0,   501, 190, 436, 48,  592,
+               445, 223, 817, 393, 340, 501, 0,   475, 545, 453, 877,
+               134, 252, 682, 82,  313, 190, 475, 0,   410, 142, 402,
+               380, 322, 272, 328, 723, 436, 545, 410, 0,   388, 812,
+               112, 230, 660, 60,  455, 48,  453, 142, 388, 0,   544,
+               536, 654, 1084, 484, 519, 592, 877, 402, 812, 544, 0),
+             nrow=11, byrow=TRUE)
> colnames(m) <- c("Воткинск", "Глазов", "Екатеринбург", "Ижевск", "Казань",
+                  "Камбарка", "Киров", "Можга", "Пермь", "Сарапул", "Уфа")
> rownames(m) <- colnames(m)
> m
             Воткинск Глазов Екатеринбург Ижевск Казань Камбарка Киров Можга Пермь Сарапул  Уфа
Воткинск            0    222          652     52    447      160   445   134   380     112  536
Глазов            222      0          594    170    565      278   223   252   322     230  654
Екатеринбург      652    594            0    600    995      708   817   682   272     660 1084
Ижевск             52    170          600      0    395      108   393    82   328      60  484
Казань            447    565          995    395      0      503   340   313   723     455  519
Камбарка          160    278          708    108    503        0   501   190   436      48  592
Киров             445    223          817    393    340      501     0   475   545     453  877
Можга             134    252          682     82    313      190   475     0   410     142  402
Пермь             380    322          272    328    723      436   545   410     0     388  812
Сарапул           112    230          660     60    455       48   453   142   388       0  544
Уфа               536    654         1084    484    519      592   877   402   812     544    0
# … создадим TSP-объект…
> tsp <- TSP(m)
# … и найдем решение методом "2-opt":
> tour <- solve_TSP(tsp, method="2-opt")
> tour
object of class ‘TOUR’ 
result of method ‘2-opt’ for 11 cities
tour length: 3080
# Получаем замкнутый маршрут, заданный списком городов по порядку обхода.
> labels(tour)
 [1] "Камбарка"     "Сарапул"      "Ижевск"       "Екатеринбург" "Пермь"        "Глазов"      
 [7] "Киров"        "Казань"       "Уфа"          "Можга"        "Воткинск"
> 48+60+600+272+322+223+340+519+402+134+160
[1] 3080

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

> m <- structure(c(222, 652, 52,  447, 160, 445, 134, 380, 112, 536,
+                  594, 170, 565, 278, 223, 252, 322, 230, 654,
+                  600, 995, 708, 817, 682, 272, 660, 1084,
+                  395, 108, 393, 82,  328, 60,  484,
+                  503, 340, 313, 723, 455, 519,
+                  501, 190, 436, 48,  592,
+                  475, 545, 453, 877,
+                  410, 142, 402,
+                  388, 812,
+                  544), 
+                Labels = c("Воткинск", "Глазов", "Екатеринбург", "Ижевск", "Казань",
+                           "Камбарка", "Киров", "Можга", "Пермь", "Сарапул", "Уфа"), 
+                 Size = 11L, class = "dist", Diag = FALSE, Upper = FALSE)
# Результат:
> m
             Воткинск Глазов Екатеринбург Ижевск Казань Камбарка Киров Можга Пермь Сарапул
Глазов            222                                                                     
Екатеринбург      652    594                                                              
Ижевск             52    170          600                                                 
Казань            447    565          995    395                                          
Камбарка          160    278          708    108    503                                   
Киров             445    223          817    393    340      501                          
Можга             134    252          682     82    313      190   475                    
Пермь             380    322          272    328    723      436   545   410              
Сарапул           112    230          660     60    455       48   453   142   388        
Уфа               536    654         1084    484    519      592   877   402   812     544

Отличий в использовании такой таблицы нет.

31 мар. 2013 г.

Живой журнал

Завёл себе живой журнал. Уже давно — 16 сентября 2012 года.

30 мар. 2013 г.

Картограммы в R: меняем проекцию

Для смены проекции карты в R можно использовать библиотеку rgdal:

> install.packages("rgdal")
> library("rgdal")

В Ubuntu для работы этой библиотеки требуются пакеты libgdal1-dev и libproj-dev с их зависимостями.

Мы можем скачать и прочитать шейп-файл, как указано ранее (это, кстати, существенно быстрее), однако продемонстрирую вариант подключения к открытой базе геоданных PostGIS при помощи того же rgdal:

> # Читаем список доступных слоёв базы GEN…
> ogrListLayers(dsn="PG:host=gis-lab.info user=guest dbname=gen password=guest")
 [1] "modis_tiles"              "oblasts"                  "ru_adm2_country"
 …
[26] "wrs2_landsat_rus"
> # … и загружаем слой границ России в объект rus.
> rus <- readOGR(dsn="PG:host=gis-lab.info user=guest dbname=gen password=guest",
+ layer="ru_adm2_country", verbose=T)

Проект GIS-Lab для отображения Сибирской части России и для отображения России целиком пользуется равновеликой конической проекцией Альберса с главными параллелями 52° и 64° с.ш. и главным меридианом 105° в.д.. Проекция объекта rus —

> proj4string(rus)
[1] "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs +towgs84=0,0,0"

— что делает его изображение разорванным по 180-му меридиану, в районе Чукотки. Сменим проекцию и нарисуем карту:

> rus2 <- spTransform(rus, CRS("+proj=aea +lat_1=52 +lat_2=64 +lat_0=0 +lon_0=105
+ +x_0=18500000 +y_0=0 +ellps=krass +units=m +towgs84=28,-130,-95,0,0,0,0 +no_defs"))
> plot(rus2, col="#31A35488", border=F)

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

> grd <- gridlines(rus,
+ norths = seq(10, 90, by=5), # рисуем линии каждые 5° между 10 и 90° с.ш.
+ # Отобразятся только те из них, которые попадут в область рисуемой карты.
+ easts = seq(-179.9999, 180, by=5), # Если указать -180, этот меридиан не отобразится.
+ ndiscr=360) # Число опорных точек — если указать мало,
> # координатная сетка после перепроецирования может получиться ломаной.
> # Меняем проекцию координатной сетки так же, как у карты…
> grd2 <- spTransform(grd, CRS("+proj=aea +lat_1=52 +lat_2=64 +lat_0=0 +lon_0=105
+ +x_0=18500000 +y_0=0 +ellps=krass +units=m +towgs84=28,-130,-95,0,0,0,0 +no_defs"))
> # … и добавляем её к рисунку.
> plot(grd2, add=T, col="#08519C88", lty=2)

Результат приведен в начале заметки.

Перед сменой проекции мы можем упростить контур объекта rus,

> rus.thiny <- thinnedSpatialPoly(rus,
+ tolerance=0.5, # Интенсивность упрощения, чем больше, тем проще контур.
+ minarea=0.1) # Минимальная площадь сохранямых островов.

объем данных уменьшается очень существенно, но машинное время, затраченное на этот процесс, превышает время на смену проекции и отрисовку исходного объекта вместе взятое. Результат с упрощенным контуром — ниже.

24 нояб. 2012 г.

Рисуем точечные картограммы в R: расставляем точки

Окончание. Предыдущие части:
Рисуем картограммы в R,
Рисуем фоновые картограммы в R: красим районы,
Рисуем картодиаграммы в R: бросаем пироги.

В R мы можем иллюстрировать распределение исследуемых объектов по территории расставляя на карте пропорциональное их численности множество точек. В результате у нас получится так называемая dot-density map. Вот такая:

Получена она при помощи следующего кода:

> q <- 1000
> s <- .2
> png("dot-density.png", width=5, height=5, units="in", res=100, bg="transparent")
> par(mai=c(.1,.1,1,.1))
> plot(udm[udm$ADMIN_LVL == 2 & !is.na(udm$ADMIN_LVL),], col="white", border=gray(.25))
> points(dotsInPolys(udm.TIK, round(tik$ER/q, digits=0)), cex=s, col="#0000ff88")
> points(dotsInPolys(udm.TIK, round(tik$KPRF/q, , digits=0)), cex=s, col="#ff000088")
> title(main="Распределение голосов за ЕР и КПРФ\nв муниципальных районах
+ и городских округах Удмуртии")
> legend("right", legend=c("ЕР", "КПРФ"), cex=.7, pt.cex=s, pch=1,
+ col=c("#0000ff88", "#ff000088"), bty="n",
+ title="Одна точка —\nтысяча голосов за…")
> dev.off()

Координаты точек для карты их плотности формирует функция dotsInPolys из библиотеки maptools.

Вы можете ознакомиться и с другими примерами (англ.) карт в R.

22 нояб. 2012 г.

Рисуем картодиаграммы в R: бросаем пироги

Продолжение. Предыдущие части:
Рисуем картограммы в R,
Рисуем фоновые картограммы в R: красим районы.

У карт с заливкой районов цветом в зависимости от доли партии на выборах есть существенный недостаток: на них никак не отражается плотность населения. Из-за этого искажается представление о значении того или иного региона для результатов голосования.

Избежать этого искажения можно изображая результаты выборов при помощи круговых диаграмм («пирогов») площадь которых пропорциональна числу проголосовавших:

Создано это изображение следующим образом:

> # Отсортируем районы в порядке убывания числа действительных бюлетеней
> # (большие «пироги» отрисуются раньше, это улучшает внешний вид):
> udm.TIK <- udm.TIK[order(tik$v10, decreasing=T),]
> tik <- tik[order(tik$v10, decreasing=T),]
> # Запрашиваем структуру первой строки объекта udm.TIK…
> str(udm.TIK[1,]) # … и изучаем её:
Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
  ..@ data       :'data.frame': 1 obs. of  3 variables:
  .. ..$ OSM_ID   : int -954515
  .. ..$ NAME     : Factor w/ 341 levels "Агрикольское",..: 107
  .. ..$ ADMIN_LVL: Factor w/ 7 levels "10","2","3","4",..: 5
  ..@ polygons   :List of 1
  .. ..$ :Formal class 'Polygons' [package "sp"] with 5 slots
  .. .. .. ..@ Polygons :List of 1
  .. .. .. .. ..$ :Formal class 'Polygon' [package "sp"] with 5 slots
  .. .. .. .. .. .. ..@ labpt  : num [1:2] 53.2 56.9
  .. .. .. .. .. .. ..@ area   : num 0.0469
  .. .. .. .. .. .. ..@ hole   : logi FALSE
  .. .. .. .. .. .. ..@ ringDir: int 1
  .. .. .. .. .. .. ..@ coords : num [1:272, 1:2] 53 53 53 53.1 53.1 ...
  .. .. .. ..@ plotOrder: int 1
  .. .. .. ..@ labpt    : num [1:2] 53.2 56.9
  .. .. .. ..@ ID       : chr "205"
  .. .. .. ..@ area     : num 0.0469
  ..@ plotOrder  : int 1
  ..@ bbox       : num [1:2, 1:2] 53 56.7 53.4 57
  .. ..- attr(*, "dimnames")=List of 2
  .. .. ..$ : chr [1:2] "x" "y"
  .. .. ..$ : chr [1:2] "min" "max"
  ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slots
  .. .. ..@ projargs: chr "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"

Обратите внимание на содержимое слота labpt, входящего в слот polygons (выделено красным) — это координаты, к которым прикрепляется на карте название региона. Для нас эта точка будет центром будущей круговой диаграммы.

> # Соберём долготы…
> x <- sapply(slot(udm.TIK, "polygons"), slot, "labpt")[1,]
> # … и широты для размещения круговых диаграмм
> y <- sapply(slot(udm.TIK, "polygons"), slot, "labpt")[2,]
> # Сформируем матрицу с долями партий — функция draw.pie требует именно матрицу:
> part <- matrix(
+ c(tik$ER/tik$v10, tik$KPRF/tik$v10, tik$LDPR/tik$v10, tik$SR/tik$v10,
+ 1-(tik$ER+tik$KPRF+tik$LDPR+tik$SR)/tik$v10),
+ ncol=5, dimnames=list(tik$x ,c("ЕР", "КПРФ", "ЛДПР", "СР", "др.")))
> # Закажем для партий цвета:
> color <- c("#0000ff88", "#ff000088", "#ffff0088", "#00ff0088", "transparent")
> #  "#ff000088" — красный полупрозрачный (88 — уровень прозрачности)
> # Закажем библиотеку, умеющую рисовать круговые диаграммы на карте:
> library("mapplots")
> # Подготовим фон:
> png("result.png", width=5, height=5, units="in", res=100, bg="transparent")
> par(mai=c(.1,.1,1,.1))
> plot(udm[udm$ADMIN_LVL == 2 & !is.na(udm$ADMIN_LVL),], col="white", border="gray")
> # Рисуем круговые диаграммы. Для того, чтобы площадь круга была пропорциональна
> # числу избирателей, радиус круга пропорционален квадратному корню из их числа.
> draw.pie(x, y, part, radius=sqrt(tik$v10)/1500, col=color)
> # Подпишем названия районов (мелким шрифтом и справа от центров «пирогов»)…
> text(x[grep("район", tik$x)]+0.05, y[grep("район", tik$x)],
+ labels=tik$x[grep("район", tik$x)], adj=0, cex=.5)
> # … и названия городов (нормальным шрифтом и слева от центров «пирогов»)
> text(x[grep("район", tik$x, invert=T)]-0.05, y[grep("район", tik$x, invert=T)],
+ labels=tik$x[grep("район", tik$x, invert=T)], adj=1)
> # Разместим легенду, заголовок и закроем png-устройство:
> legend("left", legend=c("ЕР", "КПРФ", "ЛДПР", "СР", "прочие"), fill=color, bty="n")
> title(main="Результаты выборов в Государственную Думу\nв муниципальных районах
+ и городских округах Удмуртии")
> dev.off()

UPD — Способ, позволяющий присоединять шкалу численности проголосовавших:

> # Сформируем матрицу с результатами партий, а не их долями:
> part <- matrix(c(tik$ER, tik$v10-tik$ER), ncol=2,
+ dimnames=list(tik$x ,c("ЕР", "прочие")))
> # Закажем для партий цвета:
> color <- c("#0000ff88", "transparent")
> plot(udm[udm$ADMIN_LVL == 2 & !is.na(udm$ADMIN_LVL),], col="white", border="gray")
> # Рисуем круговые диаграммы. Такой синтаксис обеспечивает пропорциональность
> # площади круга числу проголосовавших избирателей.
> draw.pie(x, y, part, radius=1/4, col=color)
> # Легенда для числа проголосовавших:
> legend.bubble("right", # Указываем размещение легенды, …
+ maxradius=1/4, # радиус самой большой окружности, …
+ z=round(max(tik$v10)/1000,0), # диапазон числовых подписей, …
+ txt.cex=0.5, # их размер, …
+ n=3, # и число…
+ bty="n") # а также отменяем отрисовку рамки.
> # Легенда для долей партий и их цветов:
> legend.pie("left", # Указываем размещение легенды, …
+ radius=1/4, # размер пирога, …
+ z=c(sum(tik$ER), sum(tik$v10-tik$ER)), # изображаемые на нём пропорции, …
+ col=color, # их цвета, …
+ labels=c("ЕР", "прочие"), # метки…
+ cex=0.5, # и их размер.
+ mab=2, # Корректируем отступы
+ bty="n") # а также отменяем отрисовку рамки.
> title(main="Результаты выборов в Государственную Думу\nв муниципальных районах
+ и городских округах Удмуртии")
> dev.off()

17 нояб. 2012 г.

Рисуем фоновые картограммы в R: красим районы

Продолжение. Начало здесь: Рисуем картограммы в R.

Любоваться на нарисованную нами в R карту — занятие малополезное, на ней даже надписей никаких нет. Приведу более утилитарный пример:

> # Отберём муниципальные районы и городские округа…
> udm.TIK <- udm[udm$ADMIN_LVL == 6 & !is.na(udm$ADMIN_LVL), ]
> # … и сохраним их наименования в csv-файл.
> write.csv(udm.TIK$NAME, file="tik.csv", quote = FALSE)

Выглядит файл tik.csv так:

,x
1,Сюмсинский район
2,Кизнерский район
…
30,Камбарский район

Теперь его можно открыть любимым редактором таблиц и добавить, допустим, число голосов на выборах в Государственную Думу.

,x,v10,ER,SR,KPRF,LDPR
1,Сюмсинский район,6753,3628,702,1155,1104
2,Кизнерский район,10834,6607,937,1586,1458
…
30,Камбарский район,9605,4266,1654,1636,1801

Используем его для раскраски карты:

> tik <- read.csv("~/tik.csv")
> brks <- seq(from =.3, to = .7, by = .1) # Закажем границы цветовых интервалов…
> colors <- rev(heat.colors(length(brks))) # … и цвета для них.
> part <- tik$ER/tik$v10 # Определим долю голосов избирателей, поданых за ЕР.
> png("ER.png", width=5, height=5, units="in", res=100, bg="transparent")
> par(mai=c(.1,.1,1,.1))
> plot(udm.TIK, col=colors[findInterval(part, brks, all.inside=T)], border="white")
> legend("right", legend=leglabs(brks, under="<", over=">"), fill=colors, bty="n")
> title(main="Доля голосов за ЕР\nв муниципальных районах
+ и городских округах Удмуртии")
> dev.off()

Карту результатов КПРФ на тех же выборах мы нарисовали по аналогии, только цветовую палитру заказывали при помощи библиотеки RColorBrewer, вот так:

> library("RColorBrewer")
> colors <- brewer.pal(length(brks), "Reds")

Шейп-файлы с картой Удмуртии из проекта OpenStreetMap (лицензия CC-by-SA) предоставлены GIS-Lab.

12 нояб. 2012 г.

R: Построение гистограммы со столбцами равной площади

Гистограмма со столбцами равной площади полезна для более точного, чем гистограмма со столбцами равной ширины, отражения формы плотности вероятности распределения переменной.

Функция hist умеет строить такие гистограммы, если в качестве границ интервалов передать ей значения соответствующих квантилей, которые можно получить при помощи функции quantile.

Аргумент freq функции hist принимает при таком её использовании значение по умолчанию FALSE — отображаются не частоты а вероятности попадания переменной в тот или иной интервал.

> q <- quantile(x, probs=seq(0, 1, 1/9))
> h <- hist(x, breaks=q)
> rug(x)

Результат — на рисунке ниже.

Рисуем картограммы в R

Шейп-файлы с картой России (указаны границы административно-территориального деления вплоть до муниципальных районов и городских округов) можно получить на этом сайте сообщества GIS-Lab. Отдельные шейп-файлы для регионов РФ и стран — бывших республик СССР GIS-Lab выкладывает сюда (в файлах boundary-* указаны границы АТО вплоть до районов городских округов и территориальных органов управления).

> # Закажем необходимую для работы библиотеку maptools…
> library("maptools")
> #… и загрузим шейп-файл АТО Удмуртии в объект udm:
> udm <- readShapePoly("~/projects/r-maps/RU-UD/boundary-polygon.shp",
+ proj4string = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"))

Естественно, вместо "~/projects/r-maps/RU-UD/boundary-polygon.shp" следует указать имя Вашего файла, а вместо "+proj=longlat +ellps=WGS84 +datum=WGS84" — Ваши предпочтения по проекции и системе координат (Proj.4 представление). Значение proj4string можно изменять без перезагрузки шейп-файла, например, так:

> proj4string(udm) <- CRS("+proj=merc")

Собственно, уже можно начинать рисовать.

> # Создаём файл-устройство для рисования:
> png("boundary-all.png", width=300, height=400, units="px", bg="transparent")
> # Задаём поля отступов:
> par(mar=c(0.5,0.5,0.5,0.5))
> # Рисуем все АТО в объекте udm:
> plot(udm, col="white")
> # Закрываем файл-устройство:
> dev.off()

Результат:

Естественно, отображением можно управлять. Посмотрим на объект udm подробнее:

> summary(udm)
Object of class SpatialPolygonsDataFrame
Coordinates:
       min      max
x 51.12416 54.43483
y 55.85058 58.54584
Is projected: FALSE 
proj4string :
[+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs]
Data attributes:
     OSM_ID                    NAME       ADMIN_LVL  
 Min.   : -2076064   Октябрьское :  3   8      :311  
 1st Qu.: -1078857   Первомайское:  3   6      : 30  
 Median : -1066632   Каменское   :  2   9      :  5  
 Mean   :   521079   Камское     :  2   10     :  1  
 3rd Qu.: -1017007   Кильмезское :  2   2      :  1  
 Max.   :144692757   (Other)     :341   (Other):  2  
                     NA's        :  4   NA's   :  7

Нам интересны атрибуты OSM_ID, NAME и ADMIN_LVL. Так будет выглядеть та же карта с точностью до муниципальных районов и городских округов:

> png("boundary-6.png", width=300, height=400, units="px", bg="transparent")
> par(mar=c(0.5,0.5,0.5,0.5))
> plot(udm[udm$ADMIN_LVL == 6 & !is.na(udm$ADMIN_LVL), ], col="white")
> dev.off()

Конструкция "!is.na(udm$ADMIN_LVL)" отбрасывает те объекты, у которых не указан административный уровень. Если этого не делать, получим ошибку "NAs not permitted in row index". Запятая перед закрывающей квадратной скобкой (выделена красным) позволяет избежать ошибки "undefined columns selected".

Отбор отображаемых объектов можно делать и по их имени, вот так:

> plot(udm[udm$NAME == "Малопургинский район" & !is.na(udm$NAME), ], col="white")

К сожалению, в данном шейп-файле не содержится сведений о том, какие территориальные органы управления входят в какой район. Эту информацию нужно указывать самостоятельно.

Автоконфигурирование сетевых интерфейсов в guest Ubuntu server

После установки на ноутбук ОС Ubuntu Precise server edition в качестве гостевой, возникла задача — обеспечить к ней доступ по постоянному имени не только с системы-хозяина, но и с других компьютеров ЛВС. Задача осложняется тем, что ноутбук может быть подключен к ЛВС и через разъём RJ-45, и посредством Wi-Fi. Хорошо то, что сервис DHCP предоставляется в любом случае.

Постоянное имя гостевой ОС прекрасно обеспечивает avahi-daemon. Единственный недостаток — от компьютеров ЛВС требуется поддержка Zeroconf.

Однако, эксплуатация с поддержкой инициализации то одного, то другого интерфейса омрачена следующим: если оставить для обоих сетевых интерфейсов в файле /etc/network/interfaces режим auto, задержка на попытки их инициализации (при условии, что подключен к ЛВС только один) составляет несколько минут. Выход — указание для обоих интерфейсов режима инициализации allow-hotplug и включение сервиса, отслеживающего их подключение — netplug, например.

Все настройки производятся в гостевой ОС.

20 окт. 2012 г.

Ubuntu Precise: moodle+postgresql

В двух словах: плохая идея.

Подробнее: да, поддержка СУБД PostgreSQL в Moodle заявлена, даже подключить базу удаётся (важно проконтролировать, чтобы был установлен php5-pgsql, а не php5-mysql, который может быть автоматически заказан вместо него). Нет, начальное наполнение базы не завершается из-за множественных жалоб СУБД на ошибки SQL-кода. Версии — PostgreSQL 9.1, Moodle 1.9.9. Возможно, с PostgreSQL 8.4, которая также доступна в репозитории, ситуация иная.

С MySQL Moodle в Precise Pangolin просто работает.

10 авг. 2012 г.

R: Построение полигона частот

В R существует возможность вместо гистограммы (или вместе с ней) построить полигон частот.

> x <- rnorm(100)
> hist(x)
> h1 <- hist(x)
> lines(h1$counts ~ h1$mids, col="red")
> rug(x)

Выглядеть результат будет следующим образом:

Полигон частот, построенный на гистограмме

Рисование гистограммы можно подавить, построив только полигон частот:

> h1 <- hist(x, plot=FALSE)
> plot(h1$counts ~ h1$mids, col="red", type="l", bty="n",
+ main="Полигон частот для x", xlab="x", ylab="Частота")

h1 — очень информативный объект, он содержит сведения о границах ($breaks) и серединах ($mids) интервалов, на которые разбиваются исходные данные, частоте ($counts) и относительной частоте ($density) наблюдений на каждом интервале.

> str(h1)
List of 7
 $ breaks     : num [1:11] -2.5 -2 -1.5 -1 -0.5 0 0.5 1 1.5 2 ...
 $ counts     : int [1:10] 1 5 9 9 28 18 12 11 4 3
 $ intensities: num [1:10] 0.02 0.1 0.18 0.18 0.56 0.36 0.24 0.22 0.08 0.06
 $ density    : num [1:10] 0.02 0.1 0.18 0.18 0.56 0.36 0.24 0.22 0.08 0.06
 $ mids       : num [1:10] -2.25 -1.75 -1.25 -0.75 -0.25 0.25 0.75 1.25 1.75 2.25
 $ xname      : chr "x"
 $ equidist   : logi TRUE
 - attr(*, "class")= chr "histogram"

2 сент. 2011 г.

Настройка TV-тюнера PixelView PV — BT878P+ FM.RC

Выглядит этот TV-тюнер следующим образом:

PixelView PV — BT878P+ FM.RC

и с другой стороны:

PixelView PV — BT878P+ FM.RC

Настраивается в linux одной командой:

sudo echo "options bttv card=72 tuner=23 radio=1" > /etc/modprobe.d/bttv.conf

UPD:

В Ubuntu Lucid файл /etc/modprobe.d/bttv.conf следует создавать при помощи любимого текстового редактора.

В Windows программа DScaler 4.11.17 подхватывает телесигнал при указании в качестве карты — PixelView PlayTV pro, в качестве тюнера — Philips [PAL].

22 мар. 2011 г.

GRUB 2: Парольная защита загрузки Linux в режиме восстановления

В Ubuntu Lucid загрузка Linux в режиме восстановления настроена по умолчанию так, что доступ к учетной записи root не требует авторизациии. Кроме того, разрешено свободное редактирование любого пункта меню загрузки. Исправить ситуацию можно настроив в GRUB 2 аутентификацию.

Назначим имя и пароль суперпользователя GRUB 2, добавив следующий текст в конец файла /etc/grub.d/00_header:

cat << EOF
set superusers="user1"
password user1 password1
EOF

Суперпользователь (в нашем случае — user1) имеет возможность загружать и редактировать любой пункт меню GRUB 2, а также работать с GRUB 2 в режиме командной строки. Без прохождения аутентификации эти действия становятся невозможны.

В файле /etc/grub.d/10_linux заменяем этот фрагмент:
printf "menuentry '${title}' ${CLASS} {\n" "${os}" "${version}"

на следующий:

if ${recovery} ; then
  printf "menuentry '${title}' --users user1 ${CLASS} {\n" "${os}" "${version}"
else
  printf "menuentry '${title}' ${CLASS} {\n" "${os}" "${version}"
fi

Таким образом мы разрешаем загрузку режима восстановления (recovery) только пользователю user1.

GRUB 2 поддерживает хранение пароля в виде хэша (алгоритм PBKDF2). Сформировать такой хэш можно при помощи программы grub-mkpasswd_pbkdf2, которая запрашивает пароль и отдает его хэш в стандартный вывод (более двухсот символов заменено на многоточие для удобства отображения).

Enter password:  
Reenter password: 
Your PBKDF2 is grub.pbkdf2.sha512.10000.9290F727ED06C38BA4549EF7DE25CF5…

В случае использования PBKDF2-хэша пароля, запись для пользователя user2 в файле /etc/grub.d/00_header будет выглядеть так:

password_pbkdf2 user2 grub.pbkdf2.sha512.10000.9290F727ED06C38BA4549EF7DE25CF5…

Заметка основана на этом руководстве и этой статье.

19 сент. 2010 г.

Мультизагрузка, GRUB 2 и LVM

Дважды в год мне приходится организовывать на компьютере двойную загрузку — очередной и предыдущей версий Ubuntu. Ситуацию осложняет то, что корневой раздел я традиционно размещаю в системе LVM, соответственно под /boot выделен отдельный раздел, а установщик Ubuntu при работе затирает его содержимое (с фатальными для загрузки предыдущей версии последствиями).

Наиболее удобным мне кажется следующее решение:

  1. Содержимое /boot копируется с отдельного раздела в соответствующий каталог корневой файловой системы.
  2. Строка для /boot удаляется из /etc/fstab уже установленной версии Ubuntu. Также из системы удаляется системный загрузчик.
  3. Устанавливается новая версия Ubuntu. Загрузочный раздел используется под /boot уже ею. GRUB 2 при установке автоматически формирует строку для загрузки старой версии Ubuntu.

14 мар. 2010 г.

Заголовки партитур в LilyPond

Сформировать заголовок партитуры в LilyPond можно следующим образом:
\version "2.12.1"
\pointAndClickOff
\book{
  \paper {indent = 0}
  \header {
      dedication  = "Посвящение"
      title       = "Название"
      subtitle    = "Подзаголовок"
      subsubtitle = "Подподзаголовок"
      poet        = "Имя ПОЭТА"
      instrument  = "Название инструмента"
        %Центрируется под подподзаголовком. Кроме того, центрируется вверху 
        %каждой последующей после первой страницы.
      composer    = "Имя КОМПОЗИТОРА"
      meter       = "Meter string"
        %Выравнивается по левому краю под именем поэта.
      arranger    = "Имя АРАНЖИРОВЩИКА"
      piece       = "Название пьесы"      %Эти два параметра отображаются для
      opus        = "Название сочинения"  %каждого \score{}, если там не указаны
                                          %собственные значения.
      %breakbefore      = This forces the title to start on a new page 
      %(set to ##t or ##f).
      copyright        = "© Уведомление об авторском праве"
      %Центрируется внизу первой страницы.
      tagline           = "Подзаголовок"
      %Центрируется внизу последней страницы.
  }
  \score {
    \relative c'' {
      \tempo "Slowly and tenderly" 4.=54 
      c4
    }
    \header {
      %piece = "Название пьесы 1"      %Эти два параметра подавляют аналоги из 
      opus  = "Название сочинения 1"  %главного блока \header{} если заданы здесь.
        % Остальные параметры будут подавлены в любом случае.
    }
  }
  \markup {
      Необязательный текстовый блок
  }
  \score {
    {\tempo "Allegro" 4=120 
      c'4
    }
    \header {
      piece = "Название пьесы 2"      %Эти два параметра подавляют аналоги из 
      %opus  = "Название сочинения 2"  %главного блока \header{} если заданы здесь.
        % Остальные параметры будут подавлены в любом случае.
    }
  }
}
После компиляции этого кода получится следующий результат:
Обычное заполнение полей заголовков
В случае, когда пользователю необходимо отображение, отличное от предоставляемого (например, сведения о композиторе необходимо отобразить в несколько строк), он может воспользоваться более сложным синтаксисом:
\version "2.12.1"
\pointAndClickOff
\book{
  \paper {indent = 0} % Убираем "красную строку" во всем документе.
  \header {
      dedication  = \markup \fill-line {"Посвящение" "Еще одно посвящение"}
      title       = \markup \column \center-align {"Название произведения" "в несколько строк"}
      subtitle    = \markup \fill-line {" " "Подзаголовок"}
      subsubtitle    = \markup \fill-line {"Подподзаголовок" " "}
      poet        = \markup \left-align \center-column {"Город" "Имя ПОЭТА" "Год"}
      instrument  = "Название инструмента"
      composer    = \markup \right-align \center-column {"Город" "Имя КОМПОЗИТОРА" "Год"}
      meter      = \markup {Slowly and tenderly (\teeny \note #"4." #1 = 54) }
      arranger    = \markup \right-column {"Город" "Имя АРАНЖИРОВЩИКА" "Год"}
%     tagline     = ##f %Если не указать tagline выводится значение по умолчанию: 
%                         "Music engraving by LilyPond 2.12.1—www.lilypond.org"
  }
  \score { \relative c'' { c4 } }
}
После компиляции этого кода получится следующий результат:
Необычное заполнение полей заголовков
В принципе, штатное использование полей заголовка не обязательно. Здесь находится соответствующая страница официального руководства пользователя LilyPond.

4 янв. 2010 г.

Общесистемная настройка шрифтов

Мне не нравится то, как по умолчанию выглядят шрифты в Ubuntu. Напротив, мне нравится, как выглядят шрифты Monotype, используемые маленькой фирмой, производящей мягкие окна.

Команда
$ aptitude install ttf-mscorefonts-installer
с последующей доводкой напильником gnome-appearance-properties (Система → Параметры → Внешний вид → Шрифты), решает проблему лишь частично — в firefox, например, также необходимо указать три варианта шрифта, причем, желательно, для каждого языка.

Кроме того, шрифтовые предпочтения других пользователей обслуживаемых мною машин совпадают с моими. Соответственно, хочется назначить начертания шрифтов глобально.

Для исполнения этого желания достаточно использовать пару файлов:

  • /etc/fonts/local.conf для общесистемных настроек;
  • ~/.fonts.conf для настроек пользовательских.

Содержимое этих файлов прекрасно описано здесь, однако свою версию /etc/fonts/local.conf я приведу:

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Настройки отображения шрифтов по умолчанию -->
<match target="font">
  <edit mode="assign" name="rgba"><const>none</const></edit>
  <edit mode="assign" name="hinting"><bool>false</bool></edit>
  <edit mode="assign" name="hintstyle"><const>hintfull</const></edit>
  <edit mode="assign" name="antialias"><bool>true</bool></edit>
  <edit mode="assign" name="autohint"><bool>false</bool></edit>
</match>
<!-- Включить hinting, выключить antialias для мелких шрифтов -->
<match target="font">
  <test compare="less_eq" name="pixelsize" qual="any"><double>17</double></test>
  <edit mode="assign" name="hinting"><bool>true</bool></edit>
  <edit mode="assign" name="antialias"><bool>false</bool></edit>
</match>
<!-- Включить antialias для полужирных шрифтов среднего размера -->
<match target="font">
  <test compare="more_eq" name="pixelsize" qual="any"><double>15</double></test>
  <test name="weight" compare="more"><const>medium</const></test>
  <edit name="antialias" mode="assign"><bool>true</bool></edit>
</match>
<!-- Назначить предпочтения в семействах шрифтов
(шрифты Monotype предпочтительнее всех) -->
<alias>
  <family>serif</family>
  <prefer>
    <family>Times New Roman</family>
    <family>Liberation Serif</family>
    <family>DejaVu Serif</family>
    <family>Thorndale AMT</family>
    <family>Luxi Serif</family>
    <family>Nimbus Roman No9 L</family>
    <family>Times</family>
  </prefer>
</alias>
<alias>
<family>sans-serif</family>
  <prefer>
    <family>Arial</family>
    <family>Liberation Sans</family>
    <family>DejaVu Sans</family>
    <family>Verdana</family>
    <family>Helvetica</family>
    <family>Albany AMT</family>
    <family>Luxi Sans</family>
    <family>Nimbus Sans L</family>
  </prefer>
</alias>
<alias>
  <family>sans</family>
  <prefer>
    <family>Arial</family>
    <family>Liberation Sans</family>
    <family>DejaVu Sans</family>
    <family>Verdana</family>
    <family>Helvetica</family>
    <family>Albany AMT</family>
    <family>Luxi Sans</family>
    <family>Nimbus Sans L</family>
  </prefer>
</alias>
<alias>
  <family>monospace</family>
  <prefer>
    <family>Courier New</family>
    <family>Liberation Mono</family>
    <family>DejaVu Sans Mono</family>
    <family>Cumberland AMT</family>
    <family>Luxi Mono</family>
    <family>Nimbus Mono L</family>
    <family>Courier</family>
  </prefer>
</alias>
</fontconfig>

За способ включения сглаживания у полужирных шрифтов спасибо жижеюзеру sb0y.

UPD: Добавляю ссылки на отличное описание опций и виртуозный пример управления отображением шрифтов.