ipeaGIT / accessibility

Calculate accessibility measures in R
https://ipeagit.github.io/accessibility/
Other
54 stars 9 forks source link

Add E2SFCA to floating cacthment area function #9

Closed rafapereirabr closed 1 year ago

higgicd commented 2 years ago

If you need some help, I was thinking of having a go at coding these other fca functions as we did this (using dplyr methods... I would have to put some time into translating to data.table...) for the bfca paper. But this has brought up some questions on my end. The E2SFCA for example is really just the application of a stepwise impedance function to the 2SFCA. So if you wanted an actual E2SFCA function - I could hard-code the E2SFCA in the same manner, using stepwise weights, but that seems like the wrong approach. By extension, the package presently does not offer a "built-in" stepwise impedance function... but it probably shouldn't/doesn't have to given the variety of ways these stepwise functions have been applied in the past (simple proportional weights, gaussian weights, etc.). What is nice about this package is that the core access functions can take any impedance function right now, so one could easily craft any stepwise function like so:

stepped_f <- function(travel_cost) {
  impedance_value <- case_when(travel_cost <= 10 ~ 1,
                               travel_cost <= 20 ~ .75,
                               travel_cost <= 30 ~ .5,
                               travel_cost <= 40 ~ .25,
                               TRUE ~ 0)
  return(impedance_value)
}

and use that, e.g.

fca_2sfca <- function(data,
                     ...
                     decay_function = stepped_f,
                     )

So to summarize... first issue: if the E2SFCA is just the 2SFCA with a stepped impedance function, and the 2SFCA can handle any impedance function, you probably don't need a separate E2SFCA.

Second issue: how to handle stepped functions beyond having someone just write their own? Other FCAs like the 3SFCA also used a stepped function but defined using a modified Gaussian backend. They are all slightly different - is there an elegant way to have a built-in customizable stepped function? And if not, you/we could explain how these relatively simple stepwise (or even significantly more complex continuous) custom functions can be defined and utilized in an expanded vignette, like the example above. Happy to help!

rafapereirabr commented 2 years ago

Hi @higgicd. I really wanted to pick your brain (and Anotonio's) on the other fca functions, so thanks for your thoughts here! I think the way you guys summarized / explained fca indicators in your paper is super helpful.

Regarding your 1st issue, that's a fair point. Nonetheless, we could have a E2SFCA metric option that require users to pass a decay_stepped() function, right?

Regarding your second point, you're right. Users can pass any custom decay function. Nonetheless, we're in favor of having more "built-in" decay functions for the convinience of users. I've given some though about building a decay_stepped() function but I haven't found an elegant way to make it flexible so that users can pass any number of steps they want. For now, here is an ugly sketch that accepts 3 or 4 steps and which could be expanded for more steps, but it is kind of hard coded and it's far from ideal.

library(data.table)

decay_stepped <- function(steps, decay_values) {

  impedance <- function(travel_cost) {

      if (length(steps) == 3) {
      impedance_value <- data.table::fcase(
                            travel_cost <= steps[1], decay_values[1],
                            travel_cost <= steps[2], decay_values[2],
                            travel_cost <= steps[3], decay_values[3],
                            default = 0)
      }

      if (length(steps) == 4) {
          impedance_value <- data.table::fcase(
            travel_cost <= steps[1], decay_values[1],
            travel_cost <= steps[2], decay_values[2],
            travel_cost <= steps[3], decay_values[3],
            travel_cost <= steps[4], decay_values[4],
            default = 0)
      }

    return(impedance_value)
    }

  return(impedance)
}

steps <- c(10, 20, 30)
decay_values <- c(1, .75, .5)

test <- decay_stepped(steps, decay_values)

test(05)
test(15)
test(25)
test(35)

steps <- c(10, 20, 30, 40)
decay_values <- c(1, .75, .5, 0.2)

test <- decay_stepped(steps, decay_values)

test(05)
test(15)
test(25)
test(35)
test(45)
dhersz commented 2 years ago

Hi @higgicd and @rafapereirabr.

I have included a new decay_stepped() function in https://github.com/ipeaGIT/accessibility/commit/8221d37c4ce9b65b346d3c3c3f5b08635a04f78f. It accepts as many steps as the users wishes. This is how it currently looks like:

library(accessibility)

opp_weights <- decay_stepped(
  steps = c(10, 20, 30, 40),
  weights = c(0.75, 0.5, 0.25, 0)
)

opp_weights(c(5, 15, 25, 35))
#> [1] 1.00 0.75 0.50 0.25

opp_weights(c(10, 20, 30, 40))
#> [1] 0.75 0.50 0.25 0.00

binary <- decay_stepped(steps = 50, weights = 0)
binary(c(25, 50, 75))
#> [1] 1 0 0

As you can see, the function currently assumes that the interval of each step in "open on the right" (i.e. values change AT the step, not right after it - that would be the equivalent of using < instead of <= in the functions you proposed). That seemed a bit more intuitive for me, what do you think?

rafapereirabr commented 2 years ago

This looks great, @dhersz ! Thanks!

dhersz commented 1 year ago

Closing this for now, feel free to open if you feel any of the topics raised here still need to be addressed.