proptax 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/README.md +161 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/proptax +3 -0
- data/lib/R/filter_csv.R +68 -0
- data/lib/proptax/cli.rb +89 -0
- data/lib/proptax/generators/report/cherry-picked.Rmd +296 -0
- data/lib/proptax/generators/report/default.Rmd +292 -0
- data/lib/proptax/generators/report.rb +31 -0
- data/lib/proptax/version.rb +3 -0
- data/lib/proptax.rb +153 -0
- data/proptax.gemspec +38 -0
- metadata +136 -0
data/README.md
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
# Proptax
|
2
|
+
|
3
|
+
Process property assessment reports provided by the City of Calgary and automatically report and visualize on discrepencies in the data.
|
4
|
+
|
5
|
+
I currently have three victories before Calgary's [Assessment Review Board](http://www.calgaryarb.ca/eCourtPublic/). Go to [TaxReformYYC](https://taxreformyyc.com) for more information.
|
6
|
+
|
7
|
+
This software automatically generates the reports I submit as evidence before the ARB.
|
8
|
+
|
9
|
+
# Setup
|
10
|
+
|
11
|
+
`proptax` is a command line `ruby` program developed under Ubuntu 16.04. It is free to use and entirely open source, so it is probably deployable on MacOS and maybe Windows with some massaging. If you figure it out, please document the process and submit a pull request. I will gladly add your contribution to this software.
|
12
|
+
|
13
|
+
## Dependencies
|
14
|
+
|
15
|
+
`proptax` combines and coordinates the output of multiple open source resources. In broad terms, it requires the following packages to generate the reports:
|
16
|
+
|
17
|
+
1. `gs`
|
18
|
+
2. `tesseract`
|
19
|
+
3. `enscript`
|
20
|
+
4. `pandoc`
|
21
|
+
5. `R`
|
22
|
+
|
23
|
+
The following commands will install all third party dependencies:
|
24
|
+
|
25
|
+
```
|
26
|
+
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
|
27
|
+
sudo add-apt-repository 'deb [arch=amd64,i386] https://cran.rstudio.com/bin/linux/ubuntu xenial/'
|
28
|
+
sudo apt update
|
29
|
+
sudo apt install -y ghostscript tesseract-ocr enscript pandoc r-base r-base-dev r-cran-scales libmagick++-dev mesa-common-dev libglu1-mesa-dev texlive-fonts-recommended texlive-latex-recommended
|
30
|
+
```
|
31
|
+
|
32
|
+
### R
|
33
|
+
|
34
|
+
`R` does the bulk of the data processing. It has some dependencies that are not available from Ubuntu 16.04 (Xenial) PPAs. They need to be installed into the `R` environment directly.
|
35
|
+
|
36
|
+
Execute the following to open the `R` command prompt:
|
37
|
+
|
38
|
+
```
|
39
|
+
R
|
40
|
+
```
|
41
|
+
|
42
|
+
Then, at the `>` prompt, execute the following `R` commands:
|
43
|
+
|
44
|
+
```
|
45
|
+
install.packages('knitr', dependencies = TRUE)
|
46
|
+
install.packages('scales', dependencies = TRUE)
|
47
|
+
install.packages('formattable', dependencies = TRUE)
|
48
|
+
install.packages('ggplot2', dependencies = TRUE)
|
49
|
+
```
|
50
|
+
|
51
|
+
Assuming successful installation, you can exit `R` by holding `Ctrl-D`.
|
52
|
+
|
53
|
+
## Install proptax
|
54
|
+
|
55
|
+
`proptax` is a `ruby` program. As such, you need to [install ruby](https://www.digitalocean.com/community/tutorials/how-to-install-ruby-on-rails-with-rbenv-on-ubuntu-16-04).
|
56
|
+
|
57
|
+
You install the latest release of `proptax` like this:
|
58
|
+
|
59
|
+
```
|
60
|
+
gem install proptax
|
61
|
+
```
|
62
|
+
|
63
|
+
# Usage
|
64
|
+
|
65
|
+
`proptax` reports-on and visualizes the data contained in residential property reports provided by the City of Calgary. Your property report and those of your neighbours can be obtained at [assessmentsearch.calgary.ca](https://assessmentsearch.calgary.ca).
|
66
|
+
|
67
|
+
Last year I made a whole series of super-boring [YouTube tutorials](https://www.youtube.com/playlist?list=PLkQAXLFkBnmiB8O06C2oGAoarBCVO7M9J) on how to collect and process your property data. The collection process has changed slightly, but [the first video](https://www.youtube.com/watch?v=m0zzsL0DYlI&list=PLkQAXLFkBnmiB8O06C2oGAoarBCVO7M9J&index=2) should point you in the right direction. You only get 50 reports per year for some reason, so use 'em all up (and send them to me)!
|
68
|
+
|
69
|
+
TODO: More usage instructions to come...
|
70
|
+
|
71
|
+
# Development
|
72
|
+
|
73
|
+
Install third-party software as with _Setup > Dependencies_, above.
|
74
|
+
|
75
|
+
Clone this repository:
|
76
|
+
|
77
|
+
```
|
78
|
+
git clone https://github.com/TaxReformYYC/report-generator-2018.git
|
79
|
+
```
|
80
|
+
|
81
|
+
Install `ruby` dependencies:
|
82
|
+
|
83
|
+
```
|
84
|
+
cd report-generator-2018
|
85
|
+
bin/setup
|
86
|
+
```
|
87
|
+
|
88
|
+
### Test:
|
89
|
+
|
90
|
+
```
|
91
|
+
bundle exec rake spec
|
92
|
+
```
|
93
|
+
|
94
|
+
### To execute the `proptax` script within the development environment:
|
95
|
+
|
96
|
+
```
|
97
|
+
bundle exec exe/proptax
|
98
|
+
```
|
99
|
+
|
100
|
+
### To build the gem:
|
101
|
+
|
102
|
+
```
|
103
|
+
bundle exec rake build
|
104
|
+
```
|
105
|
+
|
106
|
+
The package will be found in the `pkg/` directory.
|
107
|
+
|
108
|
+
### To install this gem onto your local machine, run:
|
109
|
+
|
110
|
+
```
|
111
|
+
bundle exec rake install
|
112
|
+
```
|
113
|
+
|
114
|
+
If that doesn't work, try this:
|
115
|
+
|
116
|
+
```
|
117
|
+
gem install pkg/proptax-0.1.0.gem # Note version number
|
118
|
+
```
|
119
|
+
|
120
|
+
Execute the program:
|
121
|
+
|
122
|
+
```
|
123
|
+
proptax
|
124
|
+
```
|
125
|
+
|
126
|
+
If installed correctly, you will see help instructions.
|
127
|
+
|
128
|
+
### To release a new version:
|
129
|
+
|
130
|
+
Update the version number in `version.rb`, and then run:
|
131
|
+
|
132
|
+
```
|
133
|
+
bundle exec rake release
|
134
|
+
```
|
135
|
+
|
136
|
+
This will create a `git` tag for the version, push `git` commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org):
|
137
|
+
|
138
|
+
# Contributing
|
139
|
+
|
140
|
+
Bug reports and pull requests are welcome.
|
141
|
+
|
142
|
+
## TODOs:
|
143
|
+
|
144
|
+
- Speed up tests. Remove setup redundancies
|
145
|
+
- Deploy auto CHANGELOG
|
146
|
+
- DRY out `R` code
|
147
|
+
- Deploy `tesseract` OCR on rasterized PDFs (as with Windows 7).
|
148
|
+
- Custom report template documentation
|
149
|
+
- Auto-install gem's third-party dependencies
|
150
|
+
- Set up wiki for use on different operating systems
|
151
|
+
- Dependencies require X11. It would be nice to run this on an Ubuntu 16.04 Server somehow
|
152
|
+
|
153
|
+
Suggestions? Contribute or [donate](https://taxreformyyc.com/donate)!
|
154
|
+
|
155
|
+
## Future:
|
156
|
+
|
157
|
+
- Basic API for property data submission, collection, and retrieval
|
158
|
+
|
159
|
+
# Licence
|
160
|
+
|
161
|
+
GNU General Public License v3.0
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "proptax"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/proptax
ADDED
data/lib/R/filter_csv.R
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
# How do this factors impact an assessment?
|
3
|
+
unknownImpact <- c('Taxation.Status', 'Assessment.Class',
|
4
|
+
'Property.Type', 'Property.Use', 'Valuation.Approach',
|
5
|
+
'Market.Adjustment', 'Community', 'Market.Area', 'Sub.Neighbourhood.Code..SNC.',
|
6
|
+
'Sub.Market.Area', 'Influences', 'Land.Use.Designation', 'Building.Count',
|
7
|
+
'Building.Type.Structure', 'Year.of.Construction', 'Quality', 'Basement.Suite',
|
8
|
+
'Walkout.Basement', 'Garage.Type', 'Fireplace.Count',
|
9
|
+
'Constructed.On.Original.Foundation', 'Modified.For.Disabled',
|
10
|
+
'Old.House.On.New.Foundation', 'Basementless', 'Penthouse')
|
11
|
+
|
12
|
+
|
13
|
+
# These factors don't directly affect the assessment
|
14
|
+
noImpact <- c('Roll.Number', 'Location.Address')
|
15
|
+
|
16
|
+
# Load data
|
17
|
+
csvFile <- commandArgs(trailingOnly = TRUE)
|
18
|
+
data <- read.csv(csvFile, header=TRUE)
|
19
|
+
|
20
|
+
# Identify common factors
|
21
|
+
headers <- c()
|
22
|
+
identical <- c()
|
23
|
+
for (col in colnames(data)) {
|
24
|
+
values = data[,col][!duplicated(data[,col])]
|
25
|
+
if (length(values) == 1) {
|
26
|
+
headers <- append(headers, col)
|
27
|
+
identical <- append(identical, toString(values))
|
28
|
+
}
|
29
|
+
}
|
30
|
+
commonFactors <- data.frame(headers, identical)
|
31
|
+
|
32
|
+
# Remove common factors
|
33
|
+
data <- data[,!(names(data) %in% commonFactors$headers)]
|
34
|
+
|
35
|
+
# Identify unknown factors
|
36
|
+
unknownFactors <- names(data[,names(data) %in% unknownImpact])
|
37
|
+
|
38
|
+
# Remove unknown factors from data set
|
39
|
+
data <- data[,!(names(data) %in% unknownFactors)]
|
40
|
+
|
41
|
+
# Remove irrelevant factors from data set, but preserve address for plot labels
|
42
|
+
rowNames <- data[,noImpact[2]]
|
43
|
+
data <- data[,!(names(data) %in% noImpact)]
|
44
|
+
|
45
|
+
# Label assessment records
|
46
|
+
rownames(data) <- rowNames
|
47
|
+
|
48
|
+
# Sum each property's lot size and total developed space
|
49
|
+
areaTotals <- rowSums(data[,-1])
|
50
|
+
|
51
|
+
# Isolate all assessed values
|
52
|
+
assessedValues <- data[,1]
|
53
|
+
|
54
|
+
# Remove the street names from the addresses
|
55
|
+
houseNumbers <- as.numeric(gsub("[^\\d]+", "", rowNames, perl=TRUE))
|
56
|
+
|
57
|
+
# Plot the best fit regression line
|
58
|
+
reg <- lm(assessedValues~areaTotals)
|
59
|
+
|
60
|
+
# Plot distances between points and the regression line
|
61
|
+
assessedDifferences <- residuals(reg)
|
62
|
+
adjustedValues <- predict(reg)
|
63
|
+
|
64
|
+
# Reconcile adjusted property values
|
65
|
+
discrepancies <- round(assessedDifferences/assessedValues*100, 2)
|
66
|
+
adjustedProperties <- data.frame(houseNumbers, assessedValues, adjustedValues, assessedDifferences, discrepancies)
|
67
|
+
|
68
|
+
print(adjustedProperties)
|
data/lib/proptax/cli.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'proptax'
|
3
|
+
require 'proptax/generators/report'
|
4
|
+
module Proptax
|
5
|
+
class CLI < Thor
|
6
|
+
check_unknown_options!
|
7
|
+
|
8
|
+
# 2016-2-29 http://stackoverflow.com/questions/14346285/how-to-make-two-thor-tasks-share-options
|
9
|
+
shared_options = [:ylimit, {
|
10
|
+
:type => :string,
|
11
|
+
:default => "10000",
|
12
|
+
:description => "Expand y-axis limits"}]
|
13
|
+
report_options = [:template, {
|
14
|
+
:type => :string,
|
15
|
+
:default => "default",
|
16
|
+
:description => "Apply specific template: [default, cherry-picked]"}]
|
17
|
+
# consolidate_options = [:consolidate, {
|
18
|
+
# :type => :boolean,
|
19
|
+
# :default => true,
|
20
|
+
# :description => "Consolidate the PDFs before generating the report"}]
|
21
|
+
|
22
|
+
desc "consolidate DIR", "Outputs CSV data extracted from 2018 property assessment reports"
|
23
|
+
def consolidate(dir)
|
24
|
+
Proptax::Consolidator.process(dir)
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "reports CSV_FILE", "Generate assessment reports"
|
28
|
+
method_option *shared_options
|
29
|
+
method_option *report_options
|
30
|
+
# method_option *consolidate_options
|
31
|
+
def reports(dir)
|
32
|
+
csv_file = 'consolidated.csv'
|
33
|
+
if options.consolidate?
|
34
|
+
`proptax consolidate #{dir} > #{csv_file}`
|
35
|
+
else
|
36
|
+
csv_file = "#{dir}/consolidated.csv"
|
37
|
+
end
|
38
|
+
Proptax::Generators::Report.start([csv_file, options])
|
39
|
+
generate_material("reports")
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "auto DIR", "Automatically create CSV file and reports"
|
43
|
+
method_option *shared_options
|
44
|
+
method_option *report_options
|
45
|
+
def auto(dir)
|
46
|
+
`proptax consolidate #{dir} > consolidated.csv`
|
47
|
+
Proptax::Generators::Report.start(['consolidated.csv', options])
|
48
|
+
generate_material("reports")
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "filter CSV_FILE", "Calculate and display assessment discrepancies"
|
52
|
+
method_option :csv,
|
53
|
+
:type => :boolean,
|
54
|
+
:default => false,
|
55
|
+
:description => "Output in CSV format"
|
56
|
+
method_option :header,
|
57
|
+
:type => :boolean,
|
58
|
+
:default => true,
|
59
|
+
:description => "Include header row in CSV format"
|
60
|
+
def filter(csv)
|
61
|
+
data_frame = `Rscript "#{__dir__}"/../R/filter_csv.R "#{csv}"`
|
62
|
+
if options[:csv]
|
63
|
+
lines = data_frame.split("\n")
|
64
|
+
# Print header
|
65
|
+
puts lines[0].squeeze(' ').split(' ').to_csv if options[:header]
|
66
|
+
|
67
|
+
# Print data (minus R-inserted integer row name)
|
68
|
+
lines[1..-1].each do |line|
|
69
|
+
puts line.squeeze(' ').split(' ')[1..-1].to_csv
|
70
|
+
end
|
71
|
+
else
|
72
|
+
puts data_frame
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
no_commands do
|
77
|
+
def generate_material(dir)
|
78
|
+
Dir.foreach(dir) do |file|
|
79
|
+
if /\.Rmd/.match(file)
|
80
|
+
puts "#{file}"
|
81
|
+
`cd "#{dir}" && Rscript -e "library('knitr'); knit('#{file}');"`
|
82
|
+
file.gsub!('.Rmd', '.md')
|
83
|
+
`cd "#{dir}" && Rscript -e "library('knitr'); pandoc('#{file}', format = 'latex');"`
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
---
|
2
|
+
title: 2017 Property Assessment Analysis
|
3
|
+
author: Prepared by reports@taxreformyyc.com
|
4
|
+
geometry: margin=1.5cm
|
5
|
+
---
|
6
|
+
|
7
|
+
```{r loadLibraries, echo=FALSE, message=FALSE}
|
8
|
+
# Load the required libraries
|
9
|
+
library(knitr)
|
10
|
+
library(scales)
|
11
|
+
library(formattable)
|
12
|
+
library(ggplot2)
|
13
|
+
```
|
14
|
+
|
15
|
+
```{r defineConstants, echo=FALSE}
|
16
|
+
# Constants
|
17
|
+
address <- "<%= address %>"
|
18
|
+
myAssessedValue <- <%= assessed_value %>
|
19
|
+
csvFile <- "<%= csv_file %>"
|
20
|
+
|
21
|
+
# Get the house number and street name
|
22
|
+
#myHouseNumber <- gsub("[^\\d]+", "", address, perl=TRUE)
|
23
|
+
m <- regexpr("^\\d+", address, perl=TRUE)
|
24
|
+
myHouseNumber <- regmatches(address, m)
|
25
|
+
myStreetName <- gsub(".*[\\d]", "", address, perl=TRUE)
|
26
|
+
|
27
|
+
# How do this factors impact an assessment?
|
28
|
+
unknownImpact <- c('Valuation.Approach', 'Assessment.Class',
|
29
|
+
'Property.Type', 'Property.Use', 'Taxation.Status',
|
30
|
+
'Community', 'Sub.Neighbourhood.Code..SNC.', 'Market.Area',
|
31
|
+
'Sub.Market.Area', 'Land.Use.Designation',
|
32
|
+
'Building.Count', 'Building.Type.Structure',
|
33
|
+
'Year.of.Construction', 'Quality', 'Basement.Suite',
|
34
|
+
'Walkout.Basement', 'Garage.Type', 'Fireplace',
|
35
|
+
'Influences', 'Market.Adjustment')
|
36
|
+
|
37
|
+
# These factors are informational and don't affect the assessment
|
38
|
+
noImpact <- c('Roll.Number', 'Location.Address')
|
39
|
+
```
|
40
|
+
|
41
|
+
```{r loadData, echo=FALSE}
|
42
|
+
# Load data
|
43
|
+
data <- read.csv(csvFile, header=TRUE)
|
44
|
+
```
|
45
|
+
|
46
|
+
```{r getMetaData, echo=FALSE}
|
47
|
+
streetAddresses <- data[,noImpact[2]]
|
48
|
+
|
49
|
+
# Remove the street names from the addresses
|
50
|
+
#houseNumbers <- as.numeric(gsub("[^\\d]+", "", streetAddresses, perl=TRUE))
|
51
|
+
m <- regexpr("^\\d+", streetAddresses, perl=TRUE)
|
52
|
+
houseNumbers <- as.numeric(regmatches(streetAddresses, m))
|
53
|
+
|
54
|
+
```
|
55
|
+
|
56
|
+
\center
|
57
|
+
`r address`
|
58
|
+
|
59
|
+
\flushleft
|
60
|
+
|
61
|
+
# Synopsis
|
62
|
+
|
63
|
+
This analysis pertains to the property located at **`r address`**.
|
64
|
+
It documents the treatment of the assessment data provided by the City of
|
65
|
+
Calgary for the pertinent property and those deemed comparable:
|
66
|
+
|
67
|
+
`r kable(data[order(data$Location.Address),]$Location.Address,
|
68
|
+
col.names=c('Comparable Properties'), align=c('l'))`
|
69
|
+
|
70
|
+
The data under investigation was obtained from
|
71
|
+
[assessmentsearch.calgary.ca](https://assessmentsearch.calgary.ca) and
|
72
|
+
is presented alongside this document in a consolidated CSV file.
|
73
|
+
|
74
|
+
# Approach Overview
|
75
|
+
|
76
|
+
The data investigated in this analysis consists of properties chosen for their
|
77
|
+
similar features and proximity to one another. The number of properties chosen
|
78
|
+
was maximized to ensure a fair representation of valuations and to support the
|
79
|
+
integrity of this report and its conclusion.
|
80
|
+
|
81
|
+
Given that there are many factors contained in the data whose precise impact on
|
82
|
+
assessed values are unknown, commonalities are identified and omitted from
|
83
|
+
consideration, as all such factors should have identical impact on the
|
84
|
+
final assessment.
|
85
|
+
|
86
|
+
Factors that vary are identified and presented for consideration, as
|
87
|
+
transparency and integrity is of the utmost importance. Again, the precise
|
88
|
+
impact of these factors on the final assessment is unknown, as the weights
|
89
|
+
assigned by the City of Calgary are not divulged in the assessment data they
|
90
|
+
provide.
|
91
|
+
|
92
|
+
Having acknowledged the factors that cannot easily be quantified, the focus
|
93
|
+
turns to the properties' lot sizes, total developed area, and assessed values.
|
94
|
+
By visualizing the relationship between these quantifiable factors, the
|
95
|
+
pertinent property's assessed value is contrasted with those of the selected
|
96
|
+
properties. The conclusion of this analysis is drawn from the underlying data.
|
97
|
+
|
98
|
+
# Identify common factors
|
99
|
+
|
100
|
+
Many of the factors that impact the assessments are identical. This data can
|
101
|
+
safely be removed from consideration, as the impact on the assessed values
|
102
|
+
should be the same for all the properties under investigation.
|
103
|
+
|
104
|
+
```{r commonFactors, echo=FALSE}
|
105
|
+
# Identify common factors
|
106
|
+
headers <- c()
|
107
|
+
identical <- c()
|
108
|
+
displayHeaders <- c()
|
109
|
+
for (col in colnames(data)) {
|
110
|
+
values = data[,col][!duplicated(data[,col])]
|
111
|
+
if (length(values) == 1) {
|
112
|
+
headers <- append(headers, col)
|
113
|
+
displayHeaders <-append(displayHeaders, gsub("\\.", " ", col, perl=TRUE))
|
114
|
+
identical <- append(identical, toString(values))
|
115
|
+
}
|
116
|
+
}
|
117
|
+
commonFactors <- data.frame(headers, displayHeaders, identical)
|
118
|
+
```
|
119
|
+
|
120
|
+
Here, **`r length(commonFactors$headers)`** common factors can safely be
|
121
|
+
removed from the data set:
|
122
|
+
|
123
|
+
`r kable(data.frame(commonFactors$displayHeaders, commonFactors$identical),
|
124
|
+
col.names=c('Factors', 'Identical Values'), align=c('l', 'r'))`
|
125
|
+
|
126
|
+
```{r removeCommonFactors, echo=FALSE}
|
127
|
+
# Remove common factors
|
128
|
+
data <- data[,!(names(data) %in% commonFactors$headers)]
|
129
|
+
```
|
130
|
+
|
131
|
+
|
132
|
+
# Identify unknown and non-impacting factors
|
133
|
+
|
134
|
+
Of the **`r length(colnames(data))`** remaining columns, some cannot be
|
135
|
+
quantified. Others certainly impact the assessed value of a property, but the
|
136
|
+
assessment data provided by the City of Calgary does not reveal to what extent.
|
137
|
+
|
138
|
+
## Non-impacting factors
|
139
|
+
|
140
|
+
```{r noImpactDisplayHeaders, echo=FALSE}
|
141
|
+
# Remove the dots from the header name
|
142
|
+
noImpactDisplayHeaders <- gsub("\\.", " ", noImpact, perl=TRUE)
|
143
|
+
```
|
144
|
+
|
145
|
+
These factors cannot be quantified and are administrative in purpose:
|
146
|
+
|
147
|
+
`r kable(noImpactDisplayHeaders, col.names=c('Non-Impacting Factors'))`
|
148
|
+
|
149
|
+
```{r removeIrrelevantFactors, echo=FALSE}
|
150
|
+
data <- data[,!(names(data) %in% noImpact)]
|
151
|
+
```
|
152
|
+
|
153
|
+
These are removed and the remaining **`r length(colnames(data))`** columns are
|
154
|
+
carried forward.
|
155
|
+
|
156
|
+
## Unknown factors
|
157
|
+
|
158
|
+
The impact these remaining columns have on assessment values is unknown:
|
159
|
+
|
160
|
+
```{r identifyUnknowns, echo=FALSE}
|
161
|
+
# Identify unknown factors
|
162
|
+
unknownFactors <- names(data[,names(data) %in% unknownImpact])
|
163
|
+
```
|
164
|
+
|
165
|
+
`r kable(gsub("\\.", " ", unknownFactors), col.names=c('Unknown Factors'))`
|
166
|
+
|
167
|
+
The variability within these unknown columns is presented here in the interest
|
168
|
+
of transparency:
|
169
|
+
|
170
|
+
```{r consolidateUnknownFactors, echo=FALSE}
|
171
|
+
consolidatedUnknownFactors <- data[,(names(data) %in% c(unknownFactors))]
|
172
|
+
rownames(consolidatedUnknownFactors) <- houseNumbers
|
173
|
+
```
|
174
|
+
|
175
|
+
`r kable(consolidatedUnknownFactors[order(as.numeric(row.names(consolidatedUnknownFactors))),],
|
176
|
+
align=c(rep('c', length(unknownFactors))),
|
177
|
+
row.names=TRUE,
|
178
|
+
col.names=gsub("\\.", " ", unknownFactors))`
|
179
|
+
|
180
|
+
These undoubtedly have an impact on the valuation, but their precise weighting
|
181
|
+
and significance are not presented in the assessment data provided by the City
|
182
|
+
of Calgary. As such, they are removed from the dataset.
|
183
|
+
|
184
|
+
```{r removeUnknownFactors, echo=FALSE}
|
185
|
+
# Remove unknown factors from data set
|
186
|
+
data <- data[,!(names(data) %in% unknownFactors)]
|
187
|
+
```
|
188
|
+
|
189
|
+
The remaining **`r length(colnames(data))`** columns contain the following data:
|
190
|
+
|
191
|
+
```{r addRowNamesToData, echo=FALSE}
|
192
|
+
# Add row names to data
|
193
|
+
rownames(data) <- houseNumbers
|
194
|
+
```
|
195
|
+
|
196
|
+
`r kable(data[order(as.numeric(row.names(data))),], row.names=TRUE, col.names=gsub("\\.", " ", colnames(data)))`
|
197
|
+
|
198
|
+
# Visualization
|
199
|
+
|
200
|
+
The raw data presented above is summarized in Figure 1. It
|
201
|
+
illustrates the disparity between the assessed property values. The pertinent
|
202
|
+
property is coloured red.
|
203
|
+
|
204
|
+
The blue line running through the graph is _best fit_ for the visualized model.
|
205
|
+
It serves as a predictor, or indicator, as to where the properties in question
|
206
|
+
should be positioned.
|
207
|
+
|
208
|
+
The pertinent property's overassessment is determined by measuring the distance
|
209
|
+
between the red point and the blue line.
|
210
|
+
|
211
|
+
|
212
|
+
```{r adjustValues, echo=FALSE}
|
213
|
+
# Sum each property's lot size and total developed space
|
214
|
+
areaTotals <- rowSums(data[,-1])
|
215
|
+
|
216
|
+
# Isolate all assessed values
|
217
|
+
assessedValues <- data[,1]
|
218
|
+
|
219
|
+
# Plot the best fit regression line
|
220
|
+
reg <- lm(assessedValues~areaTotals)
|
221
|
+
|
222
|
+
# Plot distances between points and the regression line
|
223
|
+
assessedDifferences <- residuals(reg)
|
224
|
+
adjustedValues <- predict(reg)
|
225
|
+
|
226
|
+
# Reconcile adjusted property values
|
227
|
+
adjustedProperties <- data.frame(houseNumbers, adjustedValues, assessedDifferences)
|
228
|
+
```
|
229
|
+
|
230
|
+
```{r generatePlot, fig.cap=paste(address, "Overassessment", " "), fig.width=12, echo=FALSE}
|
231
|
+
plot.title <- paste(address, "and Comparable Properties", sep=" ")
|
232
|
+
plot.subtitle = 'Current Assessed Property Values'
|
233
|
+
ggplot(data, aes(x=areaTotals, y=assessedValues)) +
|
234
|
+
# Plot title
|
235
|
+
ggtitle(bquote(atop(bold(.(plot.title)), atop(italic(.(plot.subtitle)), "")))) +
|
236
|
+
theme(plot.title=element_text(size=24, hjust = 0.5)) +
|
237
|
+
|
238
|
+
# Axis labels
|
239
|
+
ylab("Assessed House Values on your Street") +
|
240
|
+
xlab("Total House and Lot Size (Sq. Feet)") +
|
241
|
+
theme(axis.title.x=element_text(size=18, face="bold", margin=margin(20,0,0,0)),
|
242
|
+
axis.title.y=element_text(size=18, face="bold", margin=margin(0,20,0,0))) +
|
243
|
+
|
244
|
+
# Axis tick labels
|
245
|
+
scale_x_continuous(labels=comma) +
|
246
|
+
<% if opts.ylimit? %>
|
247
|
+
scale_y_continuous(labels=dollar, breaks=pretty_breaks(n=10),
|
248
|
+
limits=c(min(assessedValues)-<%= opts.ylimit %>, max(assessedValues)+<%= opts.ylimit %>)) +
|
249
|
+
<% else %>
|
250
|
+
scale_y_continuous(labels=dollar, breaks=pretty_breaks(n=10)) +
|
251
|
+
<% end %>
|
252
|
+
# Scatter plot points
|
253
|
+
geom_point(shape=ifelse(assessedValues==myAssessedValue, 16, 1),
|
254
|
+
size=ifelse(assessedValues==myAssessedValue, 5, 4),
|
255
|
+
colour=ifelse(assessedValues==myAssessedValue, "red", "blue")) +
|
256
|
+
|
257
|
+
# Point labels
|
258
|
+
geom_text(aes(label=houseNumbers), hjust=0.5, vjust=-2, size=5) +
|
259
|
+
geom_text(aes(label=paste("$", accounting(assessedValues, format="d"), sep="")),
|
260
|
+
hjust=0.5, vjust=-1.2) +
|
261
|
+
|
262
|
+
# Best fit line
|
263
|
+
geom_smooth(method=lm)
|
264
|
+
```
|
265
|
+
|
266
|
+
# Conclusion
|
267
|
+
|
268
|
+
The data investigated in this analysis describes the factors considered in
|
269
|
+
assessing the property located at `r address`.
|
270
|
+
It was collected and provided by the City of Calgary. This report set out to
|
271
|
+
quantify the disparity between the pertinent property and similar properties
|
272
|
+
in the the neighbourhood.
|
273
|
+
|
274
|
+
Common factors were identified and eliminated from the analysis. Similarly,
|
275
|
+
varying factors were identified, catalogued, and removed from consideration. The
|
276
|
+
City of Calgary's property reports do not describe how these characteristic
|
277
|
+
features are quantified and weighted in determining a property's assessed
|
278
|
+
value. As such, they could not be included.
|
279
|
+
|
280
|
+
The conclusions that follow are drawn from comparing lot sizes, total developed
|
281
|
+
area, and assessed values. The underlying data and the overall approach have
|
282
|
+
been presented in full.
|
283
|
+
|
284
|
+
## Overvaluation: `r currency(adjustedProperties[houseNumbers==myHouseNumber,]$assessedDifferences)`
|
285
|
+
|
286
|
+
This investigation compared the assessed values with lot sizes and developed
|
287
|
+
square footage. It has revealed that that the pertinent property is overvalued
|
288
|
+
by
|
289
|
+
**`r currency(adjustedProperties[houseNumbers==myHouseNumber,]$assessedDifferences)`**.
|
290
|
+
|
291
|
+
## Corrected Assessed Value: `r currency(adjustedProperties[houseNumbers==myHouseNumber,]$adjustedValues)`
|
292
|
+
|
293
|
+
In order to bring the pertinent property's assessed value in line with those of
|
294
|
+
similar properties in the neighbourhood, it must be reassessed at
|
295
|
+
**`r currency(adjustedProperties[houseNumbers==myHouseNumber,]$adjustedValues)`**.
|
296
|
+
|