Project

General

Profile

« Previous | Next » 

Revision e0b5cc14

Added by Benoit Parmentier over 10 years ago

assessment NEX run part3: major updates to generate stat and fig for specific tiles

View differences:

climate/research/oregon/interpolation/global_run_scalingup_assessment_part3.R
5 5
#Analyses, figures, tables and data are also produced in the script.
6 6
#AUTHOR: Benoit Parmentier 
7 7
#CREATED ON: 05/21/2014  
8
#MODIFIED ON: 05/21/2014            
8
#MODIFIED ON: 06/01/2014            
9 9
#Version: 1
10 10
#PROJECT: Environmental Layers project                                     
11 11
#################################################################################################
......
44 44
function_assessment_by_tile <- "results_interpolation_date_output_analyses_05212014.R"
45 45
#source(file.path(script_path,"results_interpolation_date_output_analyses_08052013.R"))
46 46

  
47
script_path<-"/home/parmentier/Data/IPLANT_project/env_layers_scripts/" #path to script
48
source(file.path(script_path,function_analyses_paper1)) #source all functions used in this script 1.
49
source(file.path(script_path,function_analyses_paper2)) #source all functions used in this script 2.
50
source(file.path(script_path,function_assessment_by_tile)) #source all functions used in this script 2.
51

  
52 47
load_obj <- function(f)
53 48
{
54 49
  env <- new.env()
......
78 73
#if(Atlas_server==TRUE){
79 74
#
80 75
#}
76
script_path<-"/home/parmentier/Data/IPLANT_project/env_layers_scripts/" #path to script
77
source(file.path(script_path,function_analyses_paper1)) #source all functions used in this script 1.
78
source(file.path(script_path,function_analyses_paper2)) #source all functions used in this script 2.
79
source(file.path(script_path,function_assessment_by_tile)) #source all functions used in this script 2.
81 80

  
82 81
#in_dir1 <- "/data/project/layers/commons/NEX_data/test_run1_03232014/output" #On Atlas
83 82
#parent output dir : contains subset of the data produced on NEX
84
in_dir1 <- "/data/project/layers/commons/NEX_data/output_run2_05122014/output/"
83
in_dir1 <- "/data/project/layers/commons/NEX_data/output_run3_global_analyses_05292014/output/"
85 84
# parent output dir for the curent script analyes
86
out_dir <-"/data/project/layers/commons/NEX_data/" #On NCEAS Atlas
85
out_dir <- "/data/project/layers/commons/NEX_data/output_run3_global_analyses_05292014/" #On NCEAS Atlas
87 86
# input dir containing shapefiles defining tiles
88
in_dir_shp <- "/data/project/layers/commons/NEX_data/output_run2_05122014/output/subset/shapefiles"
87
in_dir_shp <- "/data/project/layers/commons/NEX_data/output_run3_global_analyses_05292014/output/subset/shapefiles"
89 88

  
90 89
#On NEX
91 90
#contains all data from the run by Alberto
......
96 95

  
97 96
y_var_name <- "dailyTmax"
98 97
interpolation_method <- c("gam_CAI")
99
out_prefix<-"run2_global_analyses_05122014"
98
out_prefix<-"run3_global_analyses_05292014"
100 99

  
101 100
#out_dir <-paste(out_dir,"_",out_prefix,sep="")
102
create_out_dir_param <- TRUE
101
create_out_dir_param <- FALSE
103 102

  
104 103
if(create_out_dir_param==TRUE){
105 104
  out_dir <- create_dir_fun(out_dir,out_prefix)
......
108 107
  setwd(out_dir) #use previoulsy defined directory
109 108
}
110 109
setwd(out_dir)
111

  
112
df_tile_processed <- read.table(file=file.path(out_dir,paste("df_tile_processed_",out_prefix,".txt",sep="")),sep=",")
113
#in_dir_list <- file.path(in_dir1,read.table(file.path(in_dir1,"processed.txt"))$V1)
114 110
                              
115 111
CRS_locs_WGS84<-CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +towgs84=0,0,0") #Station coords WGS84
116 112
region_name <- "USA"
......
121 117
summary_metrics_v <- read.table(file=file.path(out_dir,paste("summary_metrics_v2_NA_",out_prefix,".txt",sep="")),sep=",")
122 118
tb <- read.table(file=file.path(out_dir,paste("tb_diagnostic_v_NA","_",out_prefix,".txt",sep="")),sep=",")
123 119
#df_tile_processed <- read.table(file=file.path(out_dir,paste("df_tile_processed_",out_prefix,".txt",sep="")),sep=",")
120
df_tile_processed <- read.table(file=file.path(out_dir,paste("df_tile_processed_",out_prefix,".txt",sep="")),sep=",")
121
#in_dir_list <- file.path(in_dir1,read.table(file.path(in_dir1,"processed.txt"))$V1)
124 122

  
125 123
########################## START SCRIPT ##############################
126 124

  
127 125
#Now add things here...
128 126
#
129
selected_tiles <- c("45.0_-120.0","35.0_-115.0")
127
#selected_tiles <- c("45.0_-120.0","35.0_-115.0")
128
selected_tiles <- c("40.0_-120.0","35.0_-115.0")
129

  
130 130
##raster_prediction object : contains testing and training stations with RMSE and model object
131 131
in_dir_list <- list.files(path=in_dir1,full.names=T)
132 132
in_dir_list <- in_dir_list[grep("subset",basename(basename(in_dir_list)),invert=TRUE)] #the first one is the in_dir1
......
144 144
list_shp_reg_files <- file.path(in_dir_shp,df_tile_processed$shp_files)
145 145

  
146 146
###############
147

  
148
##Quick interactive  exploration of raster object to check possible errors
149
robj1 <- load_obj(list_raster_obj_files[[1]]) #This is tile in CA
150

  
151
names(robj1)
152
names(robj1$method_mod_obj[[1]]) #for January 1, 2010
153
names(robj1$method_mod_obj[[1]]$dailyTmax) #for January
154

  
155
names(robj1$clim_method_mod_obj[[1]]$data_month) #for January
156
names(robj1$validation_mod_month_obj[[1]]$data_s) #for January with predictions
157
#Get the number of models predicted
158
nb_mod <- length(unique(robj1$tb_diagnostic_v$pred_mod))
159

  
147 160
### Figure 1: plot location of the study area with tiles processed
148 161

  
149 162
### Figures diagnostic tile:
......
158 171
covar_obj <- lf_covar_obj[[1]] 
159 172

  
160 173
var <- "TMAX"
161
list_param_results_analyses<-list(out_dir,in_path,script_path,raster_prediction_obj,interpolation_method,
174
list_param_results_analyses<-list(out_dir,in_path_tile,script_path,raster_prediction_obj,interpolation_method,
162 175
                                  covar_obj,date_selected_results,var,out_prefix)
163 176
names(list_param_results_analyses)<-c("out_path","in_path_tile","script_path","raster_prediction_obj","interpolation_method",
164 177
                     "covar_obj","date_selected_results","var","out_prefix")
165
list_param <- list_param_results_analyses
178
#list_param <- list_param_results_analyses
166 179

  
167 180
#Run modified code from stage 5...
168 181
#plots_assessment_by_date<-function(j,list_param){
......
178 191
##Create data.frame with validation and fit metrics for a full year/full numbe of runs
179 192

  
180 193
#Call functions to create plots of metrics for validation dataset
181
tile_selected <- 6
182
tb_diagnostic_v <- subset(tb,tile_id==6)
183
metric_names<-c("rmse","mae","me","r","m50")
194
#tile_selected <- 6
195
#tb_diagnostic_v <- subset(tb,tile_id==6)
196
#metric_names<-c("rmse","mae","me","r","m50")
184 197

  
185
summary_metrics_v<- boxplot_from_tb(tb_diagnostic_v,metric_names,out_prefix,out_path) #if adding for fit need to change outprefix
198
#summary_metrics_v<- boxplot_from_tb(tb_diagnostic_v,metric_names,out_prefix,out_path) #if adding for fit need to change outprefix
186 199

  
187
names(summary_metrics_v)<-c("avg","median")
200
#names(summary_metrics_v)<-c("avg","median")
188 201

  
189
summary_month_metrics_v<- boxplot_month_from_tb(tb_diagnostic_v,metric_names,out_prefix,out_path)
202
#summary_month_metrics_v<- boxplot_month_from_tb(tb_diagnostic_v,metric_names,out_prefix,out_path)
190 203

  
191 204
#Call functions to create plots of metrics for validation dataset
192 205

  
193
metric_names<-c("rmse","mae","me","r","m50")
206
#metric_names<-c("rmse","mae","me","r","m50")
194 207

  
195
summary_metrics_v<- boxplot_from_tb(tb_diagnostic_v,metric_names,out_prefix,out_path) #if adding for fit need to change outprefix
208
#summary_metrics_v<- boxplot_from_tb(tb_diagnostic_v,metric_names,out_prefix,out_path) #if adding for fit need to change outprefix
196 209

  
197
names(summary_metrics_v)<-c("avg","median")
198

  
199
summary_month_metrics_v<- boxplot_month_from_tb(tb_diagnostic_v,metric_names,out_prefix,out_path)
210
#names(summary_metrics_v)<-c("avg","median")
200 211

  
212
#summary_month_metrics_v<- boxplot_month_from_tb(tb_diagnostic_v,metric_names,out_prefix,out_path)
201 213

  
202 214

  
203 215
#### Function to plot boxplot from data.frame table of accuracy metrics
204 216

  
205 217

  
206
### need to improve these
207
boxplot_from_tb <-function(tb_diagnostic,metric_names,out_prefix,out_path){
208
  #now boxplots and mean per models
209
  library(gdata) #Nesssary to use cbindX
210
  
211
  ### Start script
212
  y_var_name<-unique(tb_diagnostic$var_interp) #extract the name of interpolated variable: dailyTmax, dailyTmin
213
  
214
  mod_names<-sort(unique(tb_diagnostic$pred_mod)) #models that have accuracy metrics
215
  t<-melt(tb_diagnostic,
216
          #measure=mod_var, 
217
          id=c("date","pred_mod","prop"),
218
          na.rm=F)
219
  t$value<-as.numeric(t$value) #problem with char!!!
220
  avg_tb<-cast(t,pred_mod~variable,mean)
221
  avg_tb$var_interp<-rep(y_var_name,times=nrow(avg_tb))
222
  median_tb<-cast(t,pred_mod~variable,median)
223
  
224
  #avg_tb<-cast(t,pred_mod~variable,mean)
225
  tb<-tb_diagnostic
226
 
227
  #mod_names<-sort(unique(tb$pred_mod)) #kept for clarity
228
  tb_mod_list<-lapply(mod_names, function(k) subset(tb, pred_mod==k)) #this creates a list of 5 based on models names
229
  names(tb_mod_list)<-mod_names
230
  #mod_metrics<-do.call(cbind,tb_mod_list)
231
  #debug here
232
  if(length(tb_mod_list)>1){
233
    mod_metrics<-do.call(cbindX,tb_mod_list) #column bind the list??
234
  }else{
235
    mod_metrics<-tb_mod_list[[1]]
236
  }
237
  
238
  test_names<-lapply(1:length(mod_names),function(k) paste(names(tb_mod_list[[1]]),mod_names[k],sep="_"))
239
  #test names are used when plotting the boxplot for the different models
240
  names(mod_metrics)<-unlist(test_names)
241
  rows_total<-lapply(tb_mod_list,nrow)
242
  for (j in 1:length(metric_names)){
243
    metric_ac<-metric_names[j]
244
    mod_pat<-glob2rx(paste(metric_ac,"_*",sep=""))   
245
    mod_var<-grep(mod_pat,names(mod_metrics),value=TRUE) # using grep with "value" extracts the matching names     
246
    #browser()
247
    test<-mod_metrics[mod_var]
248
    png(file.path(out_path,paste("boxplot_metric_",metric_ac, out_prefix,".png", sep="")))
249
    #boxplot(test,outline=FALSE,horizontal=FALSE,cex=0.5,
250
    #        ylab=paste(metric_ac,"in degree C",sep=" "))
251
    
252
    boxplot(test,outline=FALSE,horizontal=FALSE,cex=0.5,
253
              ylab=paste(metric_ac,"in degree C",sep=" "),axisnames=FALSE,axes=FALSE)
254
    axis(1, labels = FALSE)
255
    ## Create some text labels
256
    labels <- labels<- names(test)
257
    ## Plot x axis labels at default tick marks
258
    text(1:ncol(test), par("usr")[3] - 0.25, srt = 45, adj = 1,
259
         labels = labels, xpd = TRUE)
260
    axis(2)
261
    box()
262
    #legend("bottomleft",legend=paste(names(rows_total),":",rows_total,sep=""),cex=0.7,bty="n")
263
    #title(as.character(t(paste(t(names(rows_total)),":",rows_total,sep=""))),cex=0.8)
264
    title(paste(metric_ac,"for",y_var_name,sep=" "),cex=0.8)
265
    dev.off()
266
  }
267
  
268
  avg_tb$n<-rows_total #total number of predictions on which the mean is based
269
  median_tb$n<-rows_total
270
  summary_obj<-list(avg_tb,median_tb)
271
  names(summary_obj)<-c("avg","median")
272
  return(summary_obj)  
273
}
274
#boxplot_month_from_tb(tb_diagnostic,metric_names,out_prefix,out_path)
275
## Function to display metrics by months/seasons
276
boxplot_month_from_tb <-function(tb_diagnostic,metric_names,out_prefix,out_path){
277
  
278
  #Generate boxplot per month for models and accuracy metrics
279
  #Input parameters:
280
  #1) df: data frame containing accurayc metrics (RMSE etc.) per day)
281
  #2) metric_names: metrics used for validation
282
  #3) out_prefix
283
  #
284
  
285
  #################
286
  ## BEGIN
287
  y_var_name<-unique(tb_diagnostic$var_interp) #extract the name of interpolated variable: dailyTmax, dailyTmin  
288
  date_f<-strptime(tb_diagnostic$date, "%Y%m%d")   # interpolation date being processed
289
  tb_diagnostic$month<-strftime(date_f, "%m")          # current month of the date being processed
290
  mod_names<-sort(unique(tb_diagnostic$pred_mod)) #models that have accuracy metrics
291
  tb_mod_list<-lapply(mod_names, function(k) subset(tb_diagnostic, pred_mod==k)) #this creates a list of 5 based on models names
292
  names(tb_mod_list)<-mod_names
293
  t<-melt(tb_diagnostic,
294
          #measure=mod_var, 
295
          id=c("date","pred_mod","prop","month"),
296
          na.rm=F)
297
  t$value<-as.numeric(t$value) #problem with char!!!
298
  tb_mod_m_avg <-cast(t,pred_mod+month~variable,mean) #monthly mean for every model
299
  tb_mod_m_avg$var_interp<-rep(y_var_name,times=nrow(tb_mod_m_avg))
300
  
301
  tb_mod_m_sd <-cast(t,pred_mod+month~variable,sd)   #monthly sd for every model
302
  
303
  tb_mod_m_list <-lapply(mod_names, function(k) subset(tb_mod_m_avg, pred_mod==k)) #this creates a list of 5 based on models names
304
  
305
  for (k in 1:length(mod_names)){
306
    mod_metrics <-tb_mod_list[[k]]
307
    current_mod_name<- mod_names[k]
308
    for (j in 1:length(metric_names)){    
309
      metric_ac<-metric_names[j]
310
      col_selected<-c(metric_ac,"month")
311
      test<-mod_metrics[col_selected]
312
      png(file.path(out_path,paste("boxplot_metric_",metric_ac,"_",current_mod_name,"_by_month_",out_prefix,".png", sep="")))
313
      boxplot(test[[metric_ac]]~test[[c("month")]],outline=FALSE,horizontal=FALSE,cex=0.5,
314
              ylab=paste(metric_ac,"in degree C",sep=" "),,axisnames=FALSE,axes=FALSE)
315
      #boxplot(test[[metric_ac]]~test[[c("month")]],outline=FALSE,horizontal=FALSE,cex=0.5,
316
      #        ylab=paste(metric_ac,"in degree C",sep=" "))
317
      axis(1, labels = FALSE)
318
      ## Create some text labels
319
      labels <- month.abb # abbreviated names for each month
320
      ## Plot x axis labels at default tick marks
321
      text(1:length(labels), par("usr")[3] - 0.25, srt = 45, adj = 1,
322
           labels = labels, xpd = TRUE)
323
      axis(2)
324
      box()
325
      #legend("bottomleft",legend=paste(names(rows_total),":",rows_total,sep=""),cex=0.7,bty="n")
326
      title(paste(metric_ac,"for",current_mod_name,"by month",sep=" "))
327
      dev.off()
328
    }  
329
    
330
  }
331
  summary_month_obj <-c(tb_mod_m_list,tb_mod_m_avg,tb_mod_m_sd)
332
  names(summary_month_obj)<-c("tb_list","metric_month_avg","metric_month_sd")
333
  return(summary_month_obj)  
334
}
218
# ### need to improve these
219
# boxplot_from_tb <-function(tb_diagnostic,metric_names,out_prefix,out_path){
220
#   #now boxplots and mean per models
221
#   library(gdata) #Nesssary to use cbindX
222
#   
223
#   ### Start script
224
#   y_var_name<-unique(tb_diagnostic$var_interp) #extract the name of interpolated variable: dailyTmax, dailyTmin
225
#   
226
#   mod_names<-sort(unique(tb_diagnostic$pred_mod)) #models that have accuracy metrics
227
#   t<-melt(tb_diagnostic,
228
#           #measure=mod_var, 
229
#           id=c("date","pred_mod","prop"),
230
#           na.rm=F)
231
#   t$value<-as.numeric(t$value) #problem with char!!!
232
#   avg_tb<-cast(t,pred_mod~variable,mean)
233
#   avg_tb$var_interp<-rep(y_var_name,times=nrow(avg_tb))
234
#   median_tb<-cast(t,pred_mod~variable,median)
235
#   
236
#   #avg_tb<-cast(t,pred_mod~variable,mean)
237
#   tb<-tb_diagnostic
238
#  
239
#   #mod_names<-sort(unique(tb$pred_mod)) #kept for clarity
240
#   tb_mod_list<-lapply(mod_names, function(k) subset(tb, pred_mod==k)) #this creates a list of 5 based on models names
241
#   names(tb_mod_list)<-mod_names
242
#   #mod_metrics<-do.call(cbind,tb_mod_list)
243
#   #debug here
244
#   if(length(tb_mod_list)>1){
245
#     mod_metrics<-do.call(cbindX,tb_mod_list) #column bind the list??
246
#   }else{
247
#     mod_metrics<-tb_mod_list[[1]]
248
#   }
249
#   
250
#   test_names<-lapply(1:length(mod_names),function(k) paste(names(tb_mod_list[[1]]),mod_names[k],sep="_"))
251
#   #test names are used when plotting the boxplot for the different models
252
#   names(mod_metrics)<-unlist(test_names)
253
#   rows_total<-lapply(tb_mod_list,nrow)
254
#   for (j in 1:length(metric_names)){
255
#     metric_ac<-metric_names[j]
256
#     mod_pat<-glob2rx(paste(metric_ac,"_*",sep=""))   
257
#     mod_var<-grep(mod_pat,names(mod_metrics),value=TRUE) # using grep with "value" extracts the matching names     
258
#     #browser()
259
#     test<-mod_metrics[mod_var]
260
#     png(file.path(out_path,paste("boxplot_metric_",metric_ac, out_prefix,".png", sep="")))
261
#     #boxplot(test,outline=FALSE,horizontal=FALSE,cex=0.5,
262
#     #        ylab=paste(metric_ac,"in degree C",sep=" "))
263
#     
264
#     boxplot(test,outline=FALSE,horizontal=FALSE,cex=0.5,
265
#               ylab=paste(metric_ac,"in degree C",sep=" "),axisnames=FALSE,axes=FALSE)
266
#     axis(1, labels = FALSE)
267
#     ## Create some text labels
268
#     labels <- labels<- names(test)
269
#     ## Plot x axis labels at default tick marks
270
#     text(1:ncol(test), par("usr")[3] - 0.25, srt = 45, adj = 1,
271
#          labels = labels, xpd = TRUE)
272
#     axis(2)
273
#     box()
274
#     #legend("bottomleft",legend=paste(names(rows_total),":",rows_total,sep=""),cex=0.7,bty="n")
275
#     #title(as.character(t(paste(t(names(rows_total)),":",rows_total,sep=""))),cex=0.8)
276
#     title(paste(metric_ac,"for",y_var_name,sep=" "),cex=0.8)
277
#     dev.off()
278
#   }
279
#   
280
#   avg_tb$n<-rows_total #total number of predictions on which the mean is based
281
#   median_tb$n<-rows_total
282
#   summary_obj<-list(avg_tb,median_tb)
283
#   names(summary_obj)<-c("avg","median")
284
#   return(summary_obj)  
285
# }
286
# #boxplot_month_from_tb(tb_diagnostic,metric_names,out_prefix,out_path)
287
# ## Function to display metrics by months/seasons
288
# boxplot_month_from_tb <-function(tb_diagnostic,metric_names,out_prefix,out_path){
289
#   
290
#   #Generate boxplot per month for models and accuracy metrics
291
#   #Input parameters:
292
#   #1) df: data frame containing accurayc metrics (RMSE etc.) per day)
293
#   #2) metric_names: metrics used for validation
294
#   #3) out_prefix
295
#   #
296
#   
297
#   #################
298
#   ## BEGIN
299
#   y_var_name<-unique(tb_diagnostic$var_interp) #extract the name of interpolated variable: dailyTmax, dailyTmin  
300
#   date_f<-strptime(tb_diagnostic$date, "%Y%m%d")   # interpolation date being processed
301
#   tb_diagnostic$month<-strftime(date_f, "%m")          # current month of the date being processed
302
#   mod_names<-sort(unique(tb_diagnostic$pred_mod)) #models that have accuracy metrics
303
#   tb_mod_list<-lapply(mod_names, function(k) subset(tb_diagnostic, pred_mod==k)) #this creates a list of 5 based on models names
304
#   names(tb_mod_list)<-mod_names
305
#   t<-melt(tb_diagnostic,
306
#           #measure=mod_var, 
307
#           id=c("date","pred_mod","prop","month"),
308
#           na.rm=F)
309
#   t$value<-as.numeric(t$value) #problem with char!!!
310
#   tb_mod_m_avg <-cast(t,pred_mod+month~variable,mean) #monthly mean for every model
311
#   tb_mod_m_avg$var_interp<-rep(y_var_name,times=nrow(tb_mod_m_avg))
312
#   
313
#   tb_mod_m_sd <-cast(t,pred_mod+month~variable,sd)   #monthly sd for every model
314
#   
315
#   tb_mod_m_list <-lapply(mod_names, function(k) subset(tb_mod_m_avg, pred_mod==k)) #this creates a list of 5 based on models names
316
#   
317
#   for (k in 1:length(mod_names)){
318
#     mod_metrics <-tb_mod_list[[k]]
319
#     current_mod_name<- mod_names[k]
320
#     for (j in 1:length(metric_names)){    
321
#       metric_ac<-metric_names[j]
322
#       col_selected<-c(metric_ac,"month")
323
#       test<-mod_metrics[col_selected]
324
#       png(file.path(out_path,paste("boxplot_metric_",metric_ac,"_",current_mod_name,"_by_month_",out_prefix,".png", sep="")))
325
#       boxplot(test[[metric_ac]]~test[[c("month")]],outline=FALSE,horizontal=FALSE,cex=0.5,
326
#               ylab=paste(metric_ac,"in degree C",sep=" "),,axisnames=FALSE,axes=FALSE)
327
#       #boxplot(test[[metric_ac]]~test[[c("month")]],outline=FALSE,horizontal=FALSE,cex=0.5,
328
#       #        ylab=paste(metric_ac,"in degree C",sep=" "))
329
#       axis(1, labels = FALSE)
330
#       ## Create some text labels
331
#       labels <- month.abb # abbreviated names for each month
332
#       ## Plot x axis labels at default tick marks
333
#       text(1:length(labels), par("usr")[3] - 0.25, srt = 45, adj = 1,
334
#            labels = labels, xpd = TRUE)
335
#       axis(2)
336
#       box()
337
#       #legend("bottomleft",legend=paste(names(rows_total),":",rows_total,sep=""),cex=0.7,bty="n")
338
#       title(paste(metric_ac,"for",current_mod_name,"by month",sep=" "))
339
#       dev.off()
340
#     }  
341
#     
342
#   }
343
#   summary_month_obj <-c(tb_mod_m_list,tb_mod_m_avg,tb_mod_m_sd)
344
#   names(summary_month_obj)<-c("tb_list","metric_month_avg","metric_month_sd")
345
#   return(summary_month_obj)  
346
# }
335 347

  
336 348

  
337 349
##################### END OF SCRIPT ######################

Also available in: Unified diff