2015-06-02 12 views
8

ggplot2 dường như không có cách tích hợp để xử lý overplotting cho văn bản trên scatter plots. Tuy nhiên, tôi có một tình huống khác nhau, nơi các nhãn là những người trên một trục rời rạc và tôi tự hỏi nếu ai đó ở đây có một giải pháp tốt hơn so với những gì tôi đã làm.Cách đối phó với nhãn ggplot2 và chồng lên nhau trên trục rời

Một số mã ví dụ:

library(ggplot2) 

#some example data 
test.data = data.frame(text = c("A full commitment's what I'm thinking of", 
           "History quickly crashing through your veins", 
           "And I take A deep breath and I get real high", 
           "And again, the Internet is not something that you just dump something on. It's not a big truck."), 
         mean = c(3.5, 3, 5, 4), 
         CI.lower = c(4, 3.5, 5.5, 4.5), 
         CI.upper = c(3, 2.5, 4.5, 3.5)) 

#plot 
ggplot(test.data, aes_string(x = "text", y = "mean")) + 
    geom_point(stat="identity") + 
    geom_errorbar(aes(ymax = CI.upper, ymin = CI.lower), width = .1) + 
    scale_x_discrete(labels = test.data$text, name = "") 

enter image description here

Vì vậy, chúng ta thấy rằng các nhãn trục x là trên đầu trang của mỗi khác. Hai giải pháp cần lưu ý: 1) viết tắt nhãn và 2) thêm dòng mới vào nhãn. Trong nhiều trường hợp (1) sẽ làm, nhưng trong một số trường hợp nó không thể được thực hiện. Vì vậy, tôi đã viết một chức năng cho dòng mới thêm (\n) mỗi ký tự n'th để các dây để tránh tên chồng chéo:

library(ggplot2) 

#Inserts newlines into strings every N interval 
new_lines_adder = function(test.string, interval){ 
    #length of str 
    string.length = nchar(test.string) 
    #split by N char intervals 
    split.starts = seq(1,string.length,interval) 
    split.ends = c(split.starts[-1]-1,nchar(test.string)) 
    #split it 
    test.string = substring(test.string, split.starts, split.ends) 
    #put it back together with newlines 
    test.string = paste0(test.string,collapse = "\n") 
    return(test.string) 
} 

#a user-level wrapper that also works on character vectors, data.frames, matrices and factors 
add_newlines = function(x, interval) { 
    if (class(x) == "data.frame" | class(x) == "matrix" | class(x) == "factor") { 
    x = as.vector(x) 
    } 

    if (length(x) == 1) { 
    return(new_lines_adder(x, interval)) 
    } else { 
    t = sapply(x, FUN = new_lines_adder, interval = interval) #apply splitter to each 
    names(t) = NULL #remove names 
    return(t) 
    } 
} 

#plot again 
ggplot(test.data, aes_string(x = "text", y = "mean")) + 
    geom_point(stat="identity") + 
    geom_errorbar(aes(ymax = CI.upper, ymin = CI.lower), width = .1) + 
    scale_x_discrete(labels = add_newlines(test.data$text, 20), name = "") 

Và kết quả là: enter image description here

Sau đó, người ta có thể dành chút thời gian chơi với kích thước khoảng thời gian để tránh có quá nhiều khoảng trống giữa các nhãn.

Nếu số lượng nhãn khác nhau, loại giải pháp này không tốt, vì kích thước khoảng thời gian tối ưu thay đổi. Ngoài ra, bởi vì phông chữ bình thường không phải là một khoảng trắng, văn bản của các nhãn cũng có tác dụng trên chiều rộng, và do đó người ta phải cẩn thận hơn trong việc chọn khoảng thời gian tốt (có thể tránh điều này bằng cách sử dụng phông chữ không gian đơn) , nhưng chúng rộng hơn). Cuối cùng, hàm new_lines_adder() là ngu xuẩn ở chỗ nó sẽ tách các từ thành hai cách ngớ ngẩn mà con người sẽ không làm. Ví dụ. ở trên nó chia "hơi thở" thành "br \ vòng hoa". Người ta có thể viết lại nó để tránh vấn đề này.

Bạn cũng có thể giảm kích thước phông chữ, nhưng đây là giao dịch với khả năng đọc và thường giảm kích thước phông chữ là không cần thiết.

Cách tốt nhất để xử lý loại chèn nhãn này là gì?

+0

Tôi thường đối phó với các nhãn trùng lặp bằng cách xoay chúng: '+ chủ đề (axis.text.x = element_text (angle = 60, hjust = 1))' (nhưng nó không lý tưởng nếu chúng rất dài, vì nó tạo ra margin lớn) – scoa

Trả lời

0

Xây dựng câu trả lời và nhận xét @Stibu, giải pháp này tính số nhóm và sử dụng phân tách thông minh do Stibu phát triển, đồng thời thêm sửa lỗi cho các từ được phân tách bằng dấu gạch chéo.

Chức năng:

#Inserts newlines into strings every N interval 
new_lines_adder = function(x, interval) { 
    #add spaces after/
    x = str_replace_all(x, "/", "/ ") 
    #split at spaces 
    x.split = strsplit(x, " ")[[1]] 
    # get length of snippets, add one for space 
    lens <- nchar(x.split) + 1 
    # now the trick: split the text into lines with 
    # length of at most interval + 1 (including the spaces) 
    lines <- cumsum(lens) %/% (interval + 1) 
    # construct the lines 
    x.lines <- tapply(x.split, lines, function(line) 
    paste0(paste(line, collapse=" "), "\n"), simplify = TRUE) 
    # put everything into a single string 
    result <- paste(x.lines, collapse="") 
    #remove spaces we added after/
    result = str_replace_all(result, "/ ", "/") 
    return(result) 
} 

#wrapper for the above, meant for users 
add_newlines = function(x, total.length = 85) { 
    # make sure, x is a character array 
    x = as.character(x) 
    #determine number of groups 
    groups = length(x) 
    # apply splitter to each 
    t = sapply(x, FUN = new_lines_adder, interval = round(total.length/groups), USE.NAMES=FALSE) 
    return(t) 
} 

Tôi đã thử một số giá trị cho các đầu vào mặc định và 85 là giá trị mà kết quả văn bản là phong nha cho dữ liệu ví dụ. Bất kỳ cao hơn và "tĩnh mạch" trong nhãn 2 được di chuyển lên và được quá gần với nhãn thứ ba.

Sau đây là cách nó trông giống:

enter image description here

Tuy nhiên, nó sẽ là tốt hơn để sử dụng một biện pháp thực sự của tổng chiều rộng văn bản, chứ không phải số ký tự như phải dựa vào proxy này thường có nghĩa là các nhãn lãng phí rất nhiều không gian. Có thể người ta có thể viết lại new_lines_adder() với một số mã dựa trên strwidth để giải quyết vấn đề về độ rộng không đồng đều của các ký tự.

Tôi để lại câu hỏi này chưa được trả lời trong trường hợp ai đó có thể tìm cách để thực hiện việc này.

Tôi đã thêm hai hàm vào my personal package on github, vì vậy bất kỳ ai muốn sử dụng chúng, đều có thể tìm nạp chúng từ đó.

4

Tôi cố gắng để đặt cùng một phiên bản khác của new_lines_adder:

new_lines_adder = function(test.string, interval) { 
    #split at spaces 
    string.split = strsplit(test.string," ")[[1]] 
    # get length of snippets, add one for space 
    lens <- nchar(string.split) + 1 
    # now the trick: split the text into lines with 
    # length of at most interval + 1 (including the spaces) 
    lines <- cumsum(lens) %/% (interval + 1) 
    # construct the lines 
    test.lines <- tapply(string.split,lines,function(line) 
     paste0(paste(line,collapse=" "),"\n"),simplify = TRUE) 
    # put everything into a single string 
    result <- paste(test.lines,collapse="") 
    return(result) 
} 

Nó tách dòng chỉ ở không gian và đảm bảo rằng các dòng chứa ít nhất số ký tự do interval. Với điều này, cốt truyện của bạn trông như sau:

enter image description here

tôi sẽ không khẳng định điều này là cách tốt nhất. Nó vẫn bỏ qua rằng không phải tất cả các nhân vật đều có cùng chiều rộng. Có thể có điều gì đó tốt hơn có thể đạt được bằng cách sử dụng strwidth.

Bằng cách này: bạn có thể đơn giản hóa add_newlines như sau:

add_newlines = function(x, interval) { 

    # make sure, x is a character array 
    x = as.character(x) 
    # apply splitter to each 
    t = sapply(x, FUN = new_lines_adder, interval = interval,USE.NAMES=FALSE) 
    return(t) 
} 

Lúc đầu, as.character đảm bảo bạn có một chuỗi ký tự. Nó không làm tổn thương để làm điều đó cũng có, nếu bạn đã có một chuỗi ký tự, do đó, không cần cho mệnh đề if.

Điều khoản if tiếp theo là không cần thiết: sapply hoạt động hoàn hảo nếu x chỉ chứa một phần tử.Và bạn có thể chặn tên bằng cách đặt USE.NAMES=FALSE, sao cho bạn không cần phải xóa tên trong một dòng bổ sung.

+0

Số thích hợp có vẻ là khoảng 72. – Deleet

+1

Tôi không chắc mình hiểu ý của bạn là gì. Có 72 (ký tự) tổng chiều rộng mà tất cả các nhãn phải cùng nhau không? Cho đến bây giờ, bạn đã sử dụng 4 * 20 = 80, điều đó có vẻ hợp lý. Những gì bạn có thể làm, của coure, là viết lại 'add_newlines' sao cho nó mất tổng chiều dài của tất cả các nhãn và sau đó chia số này cho số nhãn. Vì vậy, bạn sẽ gọi 'add_newlines (văn bản test.data $, 80)' mà sau đó sẽ gọi bốn lần 'new_lines_adder (x, 80/4)'. – Stibu

+0

Đó là vì tôi đã sử dụng các chữ số (123456789) để ước tính số và các số lớn hơn các chữ cái (ví dụ: etaoinshr [9 chữ cái phổ biến nhất bằng tiếng Anh]), do đó kết quả hơi nhỏ hơn. Ý tưởng tốt với việc thêm xử lý tự động số nhóm trong 'new_lines_adder()'. Tôi sẽ thử phương pháp này. Người ta cũng có thể đặt giá trị mặc định cho 'add_newlines()' thành 80 vì điều này không nên thay đổi giữa các ô (tôi hy vọng!). – Deleet

Các vấn đề liên quan