Create pdf report of on demand ratio analyses - #21
Conversation
|
@wrridgeway Take a quick look at the output and see if anything stands out. After that we can do a code review. |
|
In the quick edit round, I also added a table for descriptive stats, which makes the initial text kind of obsolete. Just left in both for now. |
ccao-jardine
left a comment
There was a problem hiding this comment.
Great work so far, @Damonamajor! Some comments here for your next iteration.
| ) | ||
| ``` | ||
|
|
||
| ```{r cap-1} |
There was a problem hiding this comment.
I like the descriptive stats table, and normally, conventions would demand it be first! But can we move the Town-Level IAAO Statistics Table up so that it is Table 1 and is the first result listed?
Reasoning:
- Our stakeholders are busy and might not make it past the first page.
- This table is very clear, and indicates (for the Worth output) that both the model and DR stages met IAAO stats. This is really helpful context for readers who then go on to read the rest of the report
- Your intro has some helpful descriptive stats, like the total sample and population sizes.
| df$model_value != df$desk_review_value) # nolint: indentation_linter. | ||
| } | ||
|
|
||
| desc_table <- tibble( |
There was a problem hiding this comment.
I like the idea of this table! It compels the reader to think about things they might not have thought to ask. It's very information-dense, though. Hmm. Happy to workshop this together! I'm suggesting some required changes and some optional ones below, but we can chat if that's better.
[required] First, let's relabel "No Sales" to the more conventional "Unsold". Then, in our ordering, we should always list "Sold" aud "Unsold" first, and "All Properties" last, to follow a convention of having the "total" at the very end. Finally, I think we should exclude the means in favor of medians, unless maybe there's a reason for including it I'm not thinking of?
[optional] I wonder if dividing up the data into two tables might help. What do you think about formatting it so it corresponds to answering two different questions?
Median AVs for sold and unsold properties, per stage
| Model | Desk Review | |
|---|---|---|
| Sold | $ | $ |
| Unsold | $ | $ |
| All | $ | $ |
Desk Review changes for Sold and Unsold properties
| N changes | % of changes | |
|---|---|---|
| Sold | # | % |
| Unsold | # | % |
| All | # | 100% |
There was a problem hiding this comment.
I included means since the medians often say the same (before and after DR). It shows if there was a general shift up or down in the valuations.
There was a problem hiding this comment.
I also added a total AV shift to the bottom table
There was a problem hiding this comment.
Ah, okay, I get your reasoning for incorporating means and medians. Visually, it's a little easier for people to read changes left to right, and here, our goal is to communicate changes (if any) from Model to DR, rather than Median to Mean.
Can we resort this such that the first column of data is Median Model AV, followed by Median DR AV, then Mean Model AV, and Mean DR AV?
|
|
||
| {{< pagebreak >}} | ||
|
|
||
| ```{r cap-2} |
There was a problem hiding this comment.
Let's also move our Table of Neighborhood-Level Median Ratios to be right under the map of nbhds. That way, users get to peruse stats in the following order:
- Township
- Deciles
- NBHDs
Co-authored-by: Nicole Jardine <138712135+ccao-jardine@users.noreply.github.com>
Co-authored-by: Nicole Jardine <138712135+ccao-jardine@users.noreply.github.com>
|
Oh, one more request @Damonamajor: in the intro paragraph, can we list the modeling groups (Single-Family, Multi-Family, Condo?) and/or the class codes that the ratio analysis entails? |
They come from the res model. Is that considered single and multi family? |
|
New file is in the sale shared drive. Didn't make changes on the last question since I didn't know how it should be stated. |
ccao-jardine
left a comment
There was a problem hiding this comment.
Thanks for continuing your work on this!
| total_shift <- function(df) { | ||
| changed <- !is.na(df$model_value) & !is.na(df$desk_review_value) & | ||
| df$model_value != df$desk_review_value # nolint: indentation_linter. | ||
| sum(df$desk_review_value[changed] - df$model_value[changed], na.rm = TRUE) | ||
| } |
There was a problem hiding this comment.
Hm, I think the total AV shift is a little hard to interpret without more context (like the total AV of the town). What about median AV (or FMV) change? Or, we can cut it.
There was a problem hiding this comment.
I think we can just keep it with the median and mean MV of properties. This was just an alternative to the mean.
| ```{r cap-desc-av} | ||
| cap_desc_av <- glue( | ||
| "Assessed Values by Group.", | ||
| "\\newline Model and desk review assessed values for properties", | ||
| " with qualifying arm's-length sales (Sold), properties without", | ||
| " a recorded sale (Unsold), and all residential parcels (All)." | ||
| ) |
There was a problem hiding this comment.
Here and in the table, the column header says AV, but the values look like FMVs. It's important to distinguish them. Can you check these column headers? FMVs will be easier for our readers so those are preferable.
For extra clarity, it would also help to rename variable names; for example, from desk_review_value to desk_review_FMV, etc.
| print(map_diff, vp = viewport( | ||
| x = 2 / 3, y = 0, width = 1 / 3, height = 1, | ||
| just = c("left", "bottom") | ||
| )) |
There was a problem hiding this comment.
You know, the more I think about this third nbhd map, I think we should strike it because it might draw readers to the wrong conclusion.
Occasionally, DR will produce a higher FMV -- one that is higher than what the model produced, and much higher than the 2025 recent sale. I see this with flipped properties: they sold for a low price in 2025, got flipped, and now are significantly improved with larger square footage, new roof, etc. Often these properties are currently listed with much higher asking prices than the recent sale. Because the 2025 sale is included in the ratio analysis, this property's ratio "looks worse" because it is accurately assessed. As such, I don't want readers to draw the conclusion that it is a worse assessment.
| iaao_check <- function(metric, value) { | ||
| switch(metric, | ||
| "Median Ratio" = value >= 0.90 & value <= 1.10, | ||
| "COD" = value >= 5.0 & value <= 15.0, | ||
| "PRD" = value >= 0.98 & value <= 1.03, | ||
| "PRB" = value >= -0.05 & value <= 0.05, | ||
| "MKI" = value >= 0.90 & value <= 1.10, | ||
| NA | ||
| ) | ||
| } |
There was a problem hiding this comment.
I like that this is so clear! I like to think of these as global settings, and suggest the convention of moving these higher up in the quarto doc so that reviewers see these global settings before they see most of the rest of the doc. Can we move these somewhere below the input docs, and somewhere above where the Methods section starts?
| ) | ||
| } | ||
|
|
||
| iaao_color_cell <- function(metric, value, digits = 4) { |
There was a problem hiding this comment.
Same idea about the global settings: for all of these color codes, can we move these to be higher up? Can you add some comments that help the reviewer know, for example, ratio < 0.90 is blue?
| ratio < 0.90 ~ "74ADD1", | ||
| ratio < 0.95 ~ "ABD9E9", | ||
| ratio <= 1.05 ~ "66BD63", |
There was a problem hiding this comment.
Let's please use the 0.90-1.10 standard for ratio, and use one shade of green to indicate when a ratio is within standards (instead of multiple shades).
| cap_fig3 <- glue( | ||
| "IAAO Metrics: Model vs. Desk Review.", | ||
| "\\newline Green shaded band shows the IAAO acceptable range for each metric." | ||
| ) | ||
| ``` |
There was a problem hiding this comment.
Hm, thoughts on shrinking this figure of IAAO stats and moving these to be directly under Table 1 of IAAO stats?
| **`r format(n_excluded_sales, big.mark = ",")`** sales were excluded | ||
| as outliers by Valuations analysts, leaving a **final sample of | ||
| `r format(n_final_sales, big.mark = ",")` sales**. The township | ||
| contains **`r format(n_total_pins, big.mark = ",")` residential |
There was a problem hiding this comment.
Moving our issue discussion into this PR review -- let's define what "residential" means! I'd just define it however this code does it. If it uses our residential modeling groups, let's specify which groups those are, and class codes.
Co-authored-by: Nicole Jardine <138712135+ccao-jardine@users.noreply.github.com>
…thub.com:ccao-data/recurring-data-requests into 19-create-pdf-report-of-on-demand-ratio-analyses
|
New file is uploaded |
| **`r format(n_excluded_sales, big.mark = ",")`** sales were excluded | ||
| as outliers by Valuations analysts, leaving a **final sample of | ||
| `r format(n_final_sales, big.mark = ",")` sales**. The township | ||
| contains **`r format(n_total_pins, big.mark = ",")` residential parcels**. This universe includes Single and Multifamily parcels (up to 6 units) and excludes condo units. Prices range from `r town_stats$price_range`. |
There was a problem hiding this comment.
Billy should verify this when he looks at it.
Draft can be found here:
O:\CCAODATA\tmp\ratio-analysis.pdf