Skip to content

Commit f753bb0

Browse files
committed
Merge pull request #5 from mjwestgate/beta
Beta
2 parents 1a68bba + d26c84e commit f753bb0

15 files changed

+649
-610
lines changed

DESCRIPTION

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Package: circleplot
2-
Version: 0.3
3-
Date: 2014-10-08
2+
Version: 0.4
3+
Date: 2016-02-03
44
Title: Circular plots of distance and association matrices
55
Author: Martin J. Westgate <martinjwestgate@gmail.com>
66
Maintainer: Martin J. Westgate <martinjwestgate@gmail.com>
77
Description: Tools for plotting numeric or binary matrices.
8-
Depends: R (>= 3.1.0), RColorBrewer
8+
Depends: R (>= 3.1.0), RColorBrewer, cluster
99
License: GPL-2

INDEX

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
add.key Draw a simple key for a plot
2-
calc.overlap Identify species that are present in >1 datasets stored in a list
3-
clean.list Take a list of datasets and return in a standardised form
41
circleplot Draw a plot
2+
draw.circle Add a circle to an existing plot
53
make.circle Return a set of points on the circumference of a circle
64
make.long.format Convert a matrix to a data.frame
75
make.wide.format Convert a data.frame to a matrix

NAMESPACE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export(add.key, calc.overlap, clean.list, circleplot, make.circle, make.long.format, make.wide.format, point.attr)
1+
export(circleplot, draw.circle, make.circle, make.long.format, make.wide.format, point.attr)

R/data_processing.R

Lines changed: 88 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
# function to make a square matrix from a data.frame
55
make.wide.format<-function(
6-
input # result from spaa()
6+
input # result from spaa()
77
){
8+
if(class(input)!="data.frame"){stop("make.wide.format only works for class(input)=='data.frame'")}
89
# work out properties of the input
910
spp.names<-unique(c(input[, 1], input[, 2]))
1011
n.spp<-length(spp.names)
@@ -33,6 +34,7 @@ make.wide.format<-function(
3334

3435
# function to make a 3-column data.frame from a square matrix (i.e. inverse of make.wide.format)
3536
make.long.format<-function(input){
37+
if(class(input)!="matrix"){stop("make.wide.format only works for class(input)=='matrix'")}
3638
# get basic summaries
3739
asymmetric<-any(c(input==t(input))==FALSE, na.rm=TRUE)
3840
if(length(colnames(input))==0){spp.names<-paste("V", c(1:ncol(input)), sep="")
@@ -59,48 +61,102 @@ make.long.format<-function(input){
5961
}
6062

6163

62-
# Functions on lists
64+
# function to take an input (preferably in long format) and return a sensible distance matrix
65+
make.dist.format<-function(input){
66+
# get objects
67+
if(any(c("matrix", "data.frame")==class(input))==FALSE){
68+
stop("make.dist.format only accepts class matrix or data.frame")}
69+
if(class(input)=="matrix"){
70+
wide<-input
71+
long<-make.long.format(input)}
72+
if(class(input)=="data.frame"){
73+
wide<-make.wide.format(input)
74+
long<-input}
75+
# remove infinite values
76+
if(any(long[, 3]==Inf, na.rm=TRUE)){
77+
replace.locs<-which(long[, 3]==Inf)
78+
replace.vals<-max(long[-replace.locs, 3], na.rm=TRUE)*2
79+
long[replace.locs, 3]<-replace.vals}
80+
if(any(input[, 3]==-Inf, na.rm=TRUE)){
81+
replace.locs<-which(long[, 3]==-Inf)
82+
replace.vals<-min(long[-replace.locs, 3], na.rm=TRUE)
83+
if(replace.vals<0){replace.vals<-replace.vals*2}else{replace.vals<-replace.vals*0.5}
84+
long[replace.locs, 3]<-replace.vals}
85+
# make +ve definite
86+
if(min(long[, 3], na.rm=TRUE)<0){
87+
long[, 3]<-long[, 3]-min(long[, 3], na.rm=TRUE)}
88+
# invert to make into a distance
89+
long[, 3]<-max(long[, 3], na.rm=TRUE)-long[, 3]
90+
# convert to matrix, check for asymmetry
91+
asymmetric<-all(wide==t(wide), na.rm=TRUE)==FALSE
92+
if(asymmetric){
93+
wide.array<-array(data=NA, dim=c(dim(wide), 2))
94+
wide.array[,,1]<-wide
95+
wide.array[,,2]<-t(wide)
96+
wide.array<-apply(wide.array, c(1, 2), sum)
97+
colnames(wide.array)<-colnames(wide)
98+
rownames(wide.array)<-rownames(wide)
99+
result<-as.dist(wide.array)
100+
}else{
101+
result<-as.dist(wide)}
102+
# set na values to the mean (i.e. no effect on clustering)
103+
if(any(is.na(result))){
104+
result[which(is.na(result))]<-mean(result, na.rm=TRUE)}
105+
return(list(asymmetric= asymmetric, dist.matrix=result))
106+
}
107+
63108

64109
# take a list containing co-occurrence data, and return a list of the same length,
65110
# but with only those species shared among all datasets (type="AND")
66111
# or all species present in any dataset (type="OR", the default)
67-
clean.list<-function(x, type="OR"){
68-
if(any(c("AND", "OR")==type)==FALSE)stop("Specified 'type' not permitted; please specify AND or OR")
112+
clean.list<-function(x, reduce=FALSE){
69113
# first ensure that data are in the same (wide) format
70114
x<-lapply(x, function(y){
71115
if(class(y)=="data.frame"){y<-make.wide.format(y)}else{y<-as.matrix(y)}})
72116
n<-length(x)
73117
comparison<-calc.overlap(x)
74-
if(type=="OR"){
75-
all.species<-rownames(comparison)
76-
for(i in 1:n){
77-
y<-x[[i]]
78-
missing.rows<-which(comparison[, i]==FALSE)
79-
new.cols<-matrix(data=NA, nrow=nrow(y), ncol=length(missing.rows))
80-
colnames(new.cols)<-all.species[missing.rows]
81-
output <-cbind(y, new.cols)
82-
new.rows<-matrix(data=NA, nrow=length(missing.rows), ncol=ncol(output))
83-
rownames(new.rows)<-all.species[missing.rows]
84-
output <-rbind(output, new.rows)
85-
col.order<-order(colnames(output))
86-
x[[i]]<-output[col.order, col.order]
87-
}
88-
}else{
118+
if(reduce){
89119
and.test<-apply(comparison, 1, FUN=function(y){any(y==FALSE)==FALSE})
90120
keep.rows<-which(and.test)
91-
if(length(keep.rows)==0){stop("No species are present in all datasets; try type='OR'")
92-
}else{
93-
all.species<-rownames(comparison)[keep.rows]
94-
for(i in 1:n){
95-
y<-x[[i]]
96-
keep.cols<-which(sapply(colnames(y),
97-
FUN=function(z, comp){any(comp==z)}, comp=all.species))
98-
output<-y[keep.cols, keep.cols]
99-
col.order<-order(colnames(output))
100-
x[[i]]<-output[col.order, col.order]
101-
}
102-
}}
103-
return(x)
121+
if(length(keep.rows)==0){stop("No species are present in all datasets; try reduce=FALSE")}
122+
all.species<-rownames(comparison)[keep.rows]
123+
}else{all.species<-rownames(comparison)}
124+
125+
# set up a matrix that will be filled with data for entry in x
126+
nspp<-length(all.species)
127+
empty.matrix<-matrix(data=NA, nspp, nspp)
128+
colnames(empty.matrix)<-all.species; rownames(empty.matrix)<-all.species
129+
130+
# return a list of matrices, each containing all.species in the same order.
131+
result<-lapply(x, function(y, fill){
132+
spp<-rownames(fill)
133+
locations<-sapply(spp, function(z, comp){
134+
if(any(comp==z)){return(which(comp==z))}else{return(NA)}},
135+
comp=rownames(y))
136+
initial.list<-as.list(as.data.frame(y))
137+
filled.list<-lapply(initial.list, function(z, lookup){z[lookup]}, lookup=locations)
138+
empty.list <-as.list(as.data.frame(fill))
139+
final.list<-append(filled.list, empty.list[which(is.na(locations))])
140+
order.final<-sapply(names(final.list), function(z, lookup){
141+
which(lookup==z)}, lookup=colnames(fill))
142+
final.matrix<-as.matrix(as.data.frame(final.list[order.final]))
143+
rownames(final.matrix)<-spp
144+
colnames(final.matrix)<-spp
145+
return(final.matrix)
146+
}, fill= empty.matrix)
147+
148+
# clustering stage to go here - if turned off, test for identical rownames, and if missing, switch to alphabetical
149+
# perhaps convert list to array, use apply(result, c(1, 2), sum) to get clustering
150+
result.array<-array(unlist(result), dim=c(nspp, nspp, length(result)),
151+
dimnames=list(all.species, all.species, names(result)))
152+
result.sum<-apply(result.array, c(1, 2), function(z){sum(z, na.rm=TRUE)})
153+
result.dist<-make.dist.format(result.sum)
154+
# return in correct format
155+
return(list(
156+
wide=result,
157+
long=lapply(result, make.long.format),
158+
distance= result.dist$dist.matrix,
159+
asymmetric= result.dist$asymmetric))
104160
}
105161

106162

R/line_functions.R

Lines changed: 28 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -6,124 +6,71 @@ draw.curves<-function(x){
66
if(segment.test){
77
segment.list<-append(
88
list(x0= x$x[1:100], x1= x$x[2:101], y0= x$y[1:100], y1= x$y[2:101]),
9-
x[-c(1:2, 5)])
9+
x[-c(1:2)])
10+
segment.list<-segment.list[-which(names(segment.list)=="arrows")]
1011
do.call("segments", segment.list)
11-
}else{do.call("lines", x[1:4])}
12+
}else{
13+
if(any(names(x)=="arrows")){x<-x[-which(names(x)=="arrows")]}
14+
do.call("lines", x)}
1215
}
1316

1417

1518
# generate a list in which each entry is a list of line attributes, to pass to draw.curves()
1619
get.curves<-function(
17-
input
20+
points, # from calc.circleplot
21+
lines,
22+
plot.options # from set.plot.attributes
1823
)
1924
{
2025

2126
# calculate inter-point distances, to allow setting of pc.scale (to calculate curvature of lines relative to origin)
22-
point.distance<-dist(input$points[, 2:3])
27+
point.distance<-dist(points[, c("x", "y")])
2328
scale.distance<-point.distance-min(point.distance)
2429
scale.distance<-((scale.distance/max(scale.distance))*
25-
input$plot.control$line.curvature[2])+input$plot.control$line.curvature[1]
30+
plot.options$line.curvature[2])+ plot.options$line.curvature[1]
2631
scale.distance<-as.matrix(scale.distance)
2732

28-
# set line colours & widths.
29-
# Note that this works even for binary matrices, but is later ignored if line.gradient==FALSE
30-
line.cuts<-cut(input$lines$value, input$plot.control$line.breaks,
31-
include.lowest=TRUE, right=TRUE, labels=FALSE)
32-
input$lines$colour<-input$plot.control$line.cols[line.cuts]
33-
34-
# new code for setting line widths
35-
input$lines$lwd.max<-input$plot.control$line.widths[line.cuts]
36-
input$lines$lwd.min<-input$plot.control$line.widths[line.cuts]*input$plot.control$line.expansion
37-
38-
# add line to remove NA values if plot.control$na.control is not a list
39-
# this reduces the time taken to draw plots with many NA values
40-
if(class(input$plot.control$na.control)!="list"){
41-
if(any(is.na(input$lines$value))){
42-
input$lines<-input$lines[-which(is.na(input$lines$value)==TRUE), ]}}
43-
4433
# loop to calculate lines of requisite location and colour
45-
# line.list<-apply(input$lines, 1, FUN=function(x, input, distance){calc.lines(x, input, distance)},
46-
# input=input, distance=scale.distance)
47-
# for some reason, apply() fails here, while a loop works; implement a loop until this is resolved.
48-
line.list <-list()
49-
for(i in 1:nrow(input$lines)){line.list[[i]]<-calc.lines(input$lines[i, ], input, distance=scale.distance)}
50-
34+
line.list<-split(lines, c(1:nrow(lines)))
35+
line.list<-lapply(line.list, function(x, input, distance, plot.options){
36+
calc.lines(x, input, distance, plot.options)},
37+
input=points, distance=scale.distance, plot.options= plot.options)
38+
names(line.list)<-apply(lines[, 1:2], 1, function(x){paste(x, collapse="_")})
5139
return(line.list)
5240
}
5341

5442

5543
# function to pass to apply in get.curves() to calculate locations for each line
56-
calc.lines<-function(lines, input, distance)
44+
calc.lines<-function(x, points, distance, plot.options)
5745
{
58-
# sort out line inputs
59-
sp1<-as.character(lines[1])
60-
sp2<-as.character(lines[2])
61-
value<-as.numeric(lines[3])
62-
col<-as.character(lines[5])
63-
lwd.min<-as.numeric(lines[7])
64-
lwd.max<-as.numeric(lines[6])
65-
66-
# sort out other inputs
67-
points<-input$points
68-
plot.control<-input$plot.control
69-
binary<-input$binary
70-
asymmetric<-input$asymmetric
71-
7246
# sort out coords for this row
73-
row1<-which(points$label== sp1)
74-
row2<-which(points$label== sp2)
47+
row1<-which(points$labels== x$sp1)
48+
row2<-which(points$labels== x$sp2)
7549
coords<-data.frame(x= points$x[c(row1, row2)], y= points$y[c(row1, row2)])
76-
50+
7751
# find basic spatial info on these points
7852
distance.thisrun<-distance[row1, row2]
7953
coords.scaled<-triangle.coords(coords, distance.thisrun) # what coordinates should the curve be fit to?
8054

8155
# calculate the curve that fits between these points.
82-
# Note that if there are an even number of points, some will pass through the intercept, causing earlier code to fail
8356
if(coords.scaled$y[2]>0.0001){
8457
apex<-curve.apex(coords, distance.thisrun)
8558
curve.coords<-fit.quadratic(coords.scaled)
86-
new.curve<-as.list(reposition.curve(curve.coords, apex))
59+
new.curve<-reposition.curve(curve.coords, apex, coords)
8760
}else{ # i.e. if a straight line
8861
new.curve<-list(
8962
x=seq(coords$x[1], coords$x[2], length.out=101),
90-
y=seq(coords$y[1], coords$y[2], length.out=101))
63+
y=seq(coords$y[1], coords$y[2], length.out=101))
9164
}
9265

93-
# set NA behaviour
94-
if(is.na(value)){
95-
if(is.list(plot.control$na.control)){
96-
new.curve<-append(new.curve, plot.control$na.control)
97-
new.curve<-append(new.curve, list(direction=as.numeric(lines[4])))
98-
}
99-
}else{ # i.e. if this line is not a missing value (i.e. most cases).
100-
101-
# set line widths
102-
lwd.range<-lwd.max-lwd.min
103-
if(lwd.range>0){
104-
# set default line widths (0-1 range) assuming expansion >0
105-
x<-seq(-2, 2, length.out=100)
106-
line.widths<-dnorm(x, mean=0, sd=0.5)
107-
line.widths<-line.widths-min(line.widths)
108-
line.widths<-line.widths/max(line.widths)
109-
lwd.final<-(line.widths*lwd.range)+lwd.min
110-
}else{lwd.final<-lwd.max}
111-
11266
# ensure that curves run from their start to end point
113-
large.x<-which(sqrt(coords$x^2)>10^-3)
114-
if(length(large.x)>1){large.x<-1}
115-
if(large.x==1){order.test<-new.curve$x[1]-coords$x[large.x]
116-
}else{order.test<-new.curve$x[101]-coords$x[large.x]}
117-
if(sqrt(order.test^2)>10^-4){
118-
new.curve$x<-new.curve$x[101:1]
119-
new.curve$y<-new.curve$y[101:1]}
120-
# if direction states that the line be reversed, do so.
121-
if(as.numeric(lines[4])==2){
67+
first.x<-which.min(sqrt((coords$x[1]-new.curve$x)^2))
68+
if(first.x>1){
12269
new.curve$x<-new.curve$x[101:1]
12370
new.curve$y<-new.curve$y[101:1]}
12471

12572
# set line colours
126-
if(binary & plot.control$line.gradient){ # for the special case where line colours are set by point colours
73+
if(plot.options$line.gradient){ # for the special case where line colours are set by point colours
12774
# get line colours from input$points
12875
color1<-points$col[row1]
12976
color2<-points$col[row2]
@@ -133,15 +80,11 @@ calc.lines<-function(lines, input, distance)
13380
# ensure colours are in correct order
13481
distance.pos<-sqrt((new.curve$x[1]-points$x[row1])^2)
13582
if(distance.pos>0.001){colours.final<-colours.final[100:1]}
136-
}else{colours.final<-col} # in all other cases
83+
}else{colours.final<-x$col} # in all other cases
13784

13885
# export
139-
new.curve<-append(new.curve, list(
140-
col=as.character(colours.final),
141-
lwd=as.numeric(lwd.final),
142-
direction=as.numeric(lines[4])))
143-
144-
} # end if(is.na())==F
86+
new.curve<-append(new.curve, x[-c(1:3)])
87+
if(plot.options$line.gradient){new.curve$col<-colours.final}
14588

14689
return(new.curve)
14790

@@ -150,13 +93,8 @@ calc.lines<-function(lines, input, distance)
15093

15194

15295
# function to determine what kind of arrowhead to draw (if any) and then draw result from get.arrows()
153-
# note: if(asymmetric) is already called; so we only need to know whether an arrow should be drawn, and in which direction
15496
draw.arrows<-function(x, attr){
155-
invisible(switch(x$direction,
156-
1=={draw<-TRUE; reverse<-FALSE},
157-
2=={draw<-TRUE; reverse<-TRUE},
158-
3=={draw<-FALSE; reverse<-NA}))
159-
if(draw){
97+
if(x$arrows){
16098
if(length(x$col)>1){col.final<-x$col[ceiling(101*attr$distance)]
16199
}else{col.final<-x$col}
162100
polygon(get.arrows(x, attr, reverse), border=NA, col= col.final)}

0 commit comments

Comments
 (0)