Convert wind direction (degrees) into factors in a data.frame


#1

Hi there,

I have a data table with numeric wind direction values (wd). I wish to add another variable/ column to the table based on factor values using a 16 wind rose compass - string values such as North, North-northeast etc.

I tried using mutate and a range of 'ifelse' statements. Beside it obviously being clunky and very rudimentary, it didn't actually work very well. Hoping someone could please offer a solution.

> head(wind_dir)
# A tibble: 6 x 4
  date          SN    ws    wd
  <date>     <dbl> <dbl> <dbl>
1 2007-01-01 84084  21.3  34.8
2 2007-01-02 84084  19.9  37.8
3 2007-01-03 84084  19.3  38.2
4 2007-01-04 84084  21.7  15.1
5 2007-01-05 84084  14.4 359. 
6 2007-01-06 84084  13.9 355.

Thanks


#2

cut is the usual function fur chopping numbers into factors, but the trick is that you need both a vector of breaks, plus the labels for each bin. First, we can grab a table of values from the web with a little scraping:

library(rvest)
library(tidyverse)

url <- 'http://snowfence.umn.edu/Components/winddirectionanddegreeswithouttable3.htm'
page <- read_html(url)
directions_raw <- page %>% html_node('td table') %>% html_table(header = TRUE)

directions <- directions_raw %>% 
    set_names(~tolower(sub(' Direction', '', .x))) %>% 
    slice(-1) %>% 
    separate(degree, c('degree_min', 'degree_max'), sep = '\\s+-\\s+', convert = TRUE)

directions
#>    cardinal degree_min degree_max
#> 1         N     348.75      11.25
#> 2       NNE      11.25      33.75
#> 3        NE      33.75      56.25
#> 4       ENE      56.25      78.75
#> 5         E      78.75     101.25
#> 6       ESE     101.25     123.75
#> 7        SE     123.75     146.25
#> 8       SSE     146.25     168.75
#> 9         S     168.75     191.25
#> 10      SSW     191.25     213.75
#> 11       SW     213.75     236.25
#> 12      WSW     236.25     258.75
#> 13        W     258.75     281.25
#> 14      WNW     281.25     303.75
#> 15       NW     303.75     326.25
#> 16      NNW     326.25     348.75

To make cut work properly, there's an additional complication in that north is a little bit at both the bottom and top of the range (both 0 to 11.25 and 348.75-360), so you'll have to add a bit to the ends of the breaks and labels to make it work right:

wind_dir <- data_frame(
    date = structure(c(13514, 13515, 13516, 13517, 13518, 13519), class = "Date"), 
    SN = c(84084, 84084, 84084, 84084, 84084, 84084), 
    ws = c(21.3, 19.9, 19.3, 21.7, 14.4, 13.9), 
    wd = c(34.8, 37.8, 38.2, 15.1, 359, 355)
)

wind_dir <- wind_dir %>% 
    mutate(wd_cardinal = cut(
        wd, 
        breaks = c(0, directions$degree_max, 360), 
        labels = c(directions$cardinal, 'N')
    ))

wind_dir
#> # A tibble: 6 x 5
#>   date          SN    ws    wd wd_cardinal
#>   <date>     <dbl> <dbl> <dbl> <fct>      
#> 1 2007-01-01 84084  21.3  34.8 NE         
#> 2 2007-01-02 84084  19.9  37.8 NE         
#> 3 2007-01-03 84084  19.3  38.2 NE         
#> 4 2007-01-04 84084  21.7  15.1 NNE        
#> 5 2007-01-05 84084  14.4 359   N          
#> 6 2007-01-06 84084  13.9 355   N

#3

Thanks alistaire!!
Works brilliantly.


#4

You can also calculate the breaks with math :nerd_face:

library(tidyverse)

rose_breaks <- c(0, 360/32, (1/32 + (1:15 / 16)) * 360, 360)

rose_labs <- c(
  "North", "North-Northeast", "Northeast", "East-Northeast",
  "East", "East-Southeast", "Southeast", "South-Southeast",
  "South", "South-Southwest", "Southwest", "West-Southwest",
  "West", "West-Northwest", "Northwest", "North-Northwest",
  "North"
)

wind_dir <- tibble(
  wd = (0:16 / 16) * 360
)

wind_dir %>%
  mutate(
    rose = cut(
      wd,
      breaks = rose_breaks,
      labels = rose_labs,
      right = FALSE,
      include.lowest = TRUE
    )
  )
#> # A tibble: 17 x 2
#>       wd rose           
#>    <dbl> <fct>          
#>  1   0   North          
#>  2  22.5 North-Northeast
#>  3  45   Northeast      
#>  4  67.5 East-Northeast 
#>  5  90   East           
#>  6 112.  East-Southeast 
#>  7 135   Southeast      
#>  8 158.  South-Southeast
#>  9 180   South          
#> 10 202.  South-Southwest
#> 11 225   Southwest      
#> 12 248.  West-Southwest 
#> 13 270   West           
#> 14 292.  West-Northwest 
#> 15 315   Northwest      
#> 16 338.  North-Northwest
#> 17 360   North

Created on 2018-09-20 by the reprex package (v0.2.0).


#5

Thanks jcblum that works brilliantly too :+1:t2: