Dan Kidney
Dan Kidney

Reputation: 153

Perform function on subset of data

I have a set of data below which shows a number of cars at an imaginary car dealership. The 'current_price' variable is obviously the price the car is currently set to sell at. The 'minimum_price' variable shows the hard floor price that the car must not, under any circumstance, sell at. (This can be assumed to be the purchase price).

I'm trying to create a function where the user can select a subset of the cars in the database (using the 'user defined parameters' as noted below), and then reduce or increase the 'Current_Price' by either a percentage or pound(£) value.

The 'minimum profit parameters' set the minimum profit which is to be made on all cars. In this example they have been set to £10 and 10%. Meaning the profit for each car must be either £10, or 10% of the current price - whichever is larger.

The price change parameters set how much the price is to move and whether it should move up or down.

# Dummy data
Type <- rep(c("Car", "Van"),each=3)
Age <- as.numeric(c(2, 2, 5, 4, 8,1))
Colour <- c("Red", "Red", "Yellow", "Red", "Black", "Red")
Make <- c("Ford", "VW", "VW", "VW", "BMW", "Ford")
Current_Price <- as.numeric(c(1050, 1000, 1500, 995, 2200, 2100))
Minimum_Price <- as.numeric(c(900, 600, 500, 850, 1900, 1950))
df1 <- data.frame(Type, Age, Colour, Make, Current_Price, Minimum_Price)

# User defined parameters - price to be changed for all cars which fit below selection
Input_Type <- "Car"
Input_Min_Age <- 2 # All cars this age and above
Input_Max_Age <- 10 # All cars this age and below
Input_Colour <- "Red"
Input_Make <- c("Ford", "VW")

# Minimum profit parameters
Input_Min_Pounds <- 10
Input_Min_Percentage <- 0.10

# Price change parameters
Input_Change_Type <- "Percentage" # "Percentage" or "Pound"
Input_Change_Value <- -0.10 # "-" sign to represent price reduction

Given the above, I would expect lines 1 & 2 to be effected by the change. Line 1's price should move down from £1,050 to £1,000. This is because £1,000 is the lowest price possible where 10% of the price is profit (900/(1-0.10) = 1000).

Line 2's price should simply move down by 10% to 900.

Has anyone got an idea how to put this into a function which will be fairly intuitive to use for someone who is not used to using R?

Upvotes: 1

Views: 91

Answers (2)

R Yoda
R Yoda

Reputation: 8760

This answer uses a data.table to support the price changes in the original "data.frame" (as you have explained in the comments to your question) the solution could look like this.

I am still ignoring the pricing logic because I want to focus on the usability aspect (this specialized pricing logic is arbitrary and not of particular interest for anybody else here at SO; if you have a specific problem to implement it yourself please open a new question and explain the problem in detail).

library(data.table)

data <- as.data.table(df1)

calc_price <- function(Current_Price,
                       Minimum_Price,
                       price_change_type,
                       price_change_value,
                       min_profit_pounds,
                       min_profit_percentage) {
  # TODO implement your pricing logic here...
  return(Current_Price + 1)
}

update_car_prices <- function(data,
                              filter,
                              price_change_type     = c("Percentage", "Pound"),
                              price_change_value    = 0,
                              min_profit_pounds     = 10,
                              min_profit_percentage = 0.10) {

  stopifnot(is.data.table(data))

  price_change_type <- match.arg(price_change_type)  # use the first value if none was provided
  filter_exp        <- substitute(filter)            # "parse" the passed value as expression
  # date the price using a separate function to encapsulate the logic
  data[eval(filter_exp), Current_Price := calc_price(Current_Price,
                                                     Minimum_Price,
                                                     price_change_type,
                                                     price_change_value,
                                                     min_profit_pounds,
                                                     min_profit_percentage)][]
  return(data)
}

The usage is still similar to my data.frame answer, e. g.:

update_car_prices(data, Type == "Car" & Age >= 2 & Age <= 10 & Colour == "Red" & Make %in% c("Ford", "VW"))
update_car_prices(data, Colour == "Red")
update_car_prices(data, Colour == "Red", "Pound", 500)

The differences are:

  1. The whole data.table (data) is returned to see the impact
  2. The original data is changed since data.tables are passed by reference and I am updating the price "by reference" using the data.table syntax :=

Upvotes: 1

R Yoda
R Yoda

Reputation: 8760

This answer is based on a data.frame...

Your questions addresses multiple aspects (pricing logic, filtering logic and usability).

I am focusing on the usability (and ignoring the pricing logic since this is just a deliberate detail).

I see at least three options:

  1. Use a strongly typed function:

    get_car_prices1 <- function(data, Input_Type, Input_Min_Age, Input_Max_Age, Input_Colour, Input_Make, Input_Min_Pounds, Input_Min_Percentage)

  2. Use an "untyped" function with a deliberate number of arguments via ... to support filtering by passing only the required arguments:

    get_car_prices2 <- function(data, Input_Min_Pounds, Input_Min_Percentage, ...)

  3. Use meta programming with substitute + eval

I have decided for option 3 as being the best (user friendly + flexible) option IMHO:

get_car_prices <- function(data,
                           filter,
                           price_change_type  = c("Percentage", "Pound"),
                           price_change_value = 1)
{
  price_change_type <- match.arg(price_change_type)  # use the first value if none was provided
  filter_exp        <- substitute(filter)            # "parse" the passed value as expression
  data_subset       <- subset(data, eval(filter_exp))
  # TODO add your pricing logic here (e. g. using "ifelse")
  return(data_subset)
}

# Usage examples:

get_car_prices(df1, Colour == "Red")
#   Type Age Colour Make Current_Price Minimum_Price
# 1  Car   2    Red Ford          1050           900
# 2  Car   2    Red   VW          1000           600
# 4  Van   4    Red   VW           995           850
# 6  Van   1    Red Ford          2100          1950

get_car_prices(df1, Type == "Car" & Age >= 2 & Age <= 10 & Colour == "Red" & Make %in% c("Ford", "VW"))
#   Type Age Colour Make Current_Price Minimum_Price
# 1  Car   2    Red Ford          1050           900
# 2  Car   2    Red   VW          1000           600

get_car_prices(df1, Colour == "Red", "Pound", 500)
# ...

get_car_prices(df1, Colour == "Red", "dumping price", 1)
# Error in match.arg(price_change_type) : 
#   'arg' should be one of “Percentage”, “Pound” 

# But: The user has to learn at least the expression logic of R and that variables (and values) are case-sensitive:
get_car_prices(df1, Colour == "red")
# [1] Type          Age           Colour        Make          Current_Price Minimum_Price
#  <0 rows> (or 0-length row.names)

# Error: Assignment operator (=) used instead of comparison operator (==)
get_car_prices(df1, Colour = "Red")
# Error in get_car_prices(df1, Colour = "Red") : 
#   unused argument (Colour = "Red")

Upvotes: 0

Related Questions