You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
11 KiB
258 lines
11 KiB
2 months ago
|
#+options: ':nil *:t -:t ::t <:t H:3 \n:nil ^:t arch:headline author:t
|
||
|
#+options: broken-links:nil c:nil creator:nil d:(not "LOGBOOK") date:t e:t
|
||
|
#+options: email:nil expand-links:t f:t inline:t num:t p:nil pri:nil prop:nil
|
||
|
#+options: stat:t tags:t tasks:t tex:t timestamp:t title:t toc:t todo:t |:t
|
||
|
#+title: Zoopla Manchester
|
||
|
#+date: \today
|
||
|
#+author: Craig Oates
|
||
|
#+email: craig@craigoates.net
|
||
|
#+language: en
|
||
|
#+select_tags: export
|
||
|
#+exclude_tags: noexport
|
||
|
#+creator: Emacs 29.1.90 (Org mode 9.7-pre)
|
||
|
#+cite_export:
|
||
|
|
||
|
* Setup Common Lisp Environment
|
||
|
|
||
|
I’ve copied the following code block over from other files. Run it if this is
|
||
|
your first file open in the session.
|
||
|
|
||
|
#+begin_src lisp :session :results silent
|
||
|
(ql:quickload :com.inuoe.jzon) ; JSON parser.
|
||
|
(ql:quickload :dexador) ; HTTP requests.
|
||
|
(ql:quickload :plump) ; HTML/XML parser.
|
||
|
(ql:quickload :lquery) ; HTML/DOM manipulation.
|
||
|
(ql:quickload :lparallel) ; Parallel programming.
|
||
|
(ql:quickload :cl-ppcre) ; RegEx. library.
|
||
|
(ql:quickload :plot/vega) ; Vega plotting library.
|
||
|
(ql:quickload :lisp-stat) ; Stat's library.
|
||
|
(ql:quickload :data-frame) ; Data frame library eqv. to Python's Numpy.
|
||
|
(ql:quickload :str) ; String library, expands on 'string' library.
|
||
|
#+end_src
|
||
|
|
||
|
* Gather Zoopla Rent Data (Manually)
|
||
|
|
||
|
- [[https://www.zoopla.co.uk/][Zoopla]]
|
||
|
|
||
|
The site produced a single page so I didn't feel the need to scrap the data. I
|
||
|
just saved the page into =raw-data/external/2024-03-18-zoopla-rent-manc/=. The
|
||
|
data is the actual HTML and CSS, so the files will not be committed to the
|
||
|
repository, to match the established behaviour of the other sites I've already
|
||
|
looked at.
|
||
|
|
||
|
The filters I used:
|
||
|
|
||
|
- Date: 2024-03-18
|
||
|
- Location: Manchester City Centre, Greater Manchester
|
||
|
- Radius: 'This area only'
|
||
|
- Price Range: £400–£1,300 pcm
|
||
|
- Only monthly rents listed
|
||
|
|
||
|
* Clean Up and Parse Data
|
||
|
|
||
|
Going to separate the listings out into their own files, to match the
|
||
|
established behaviour with other sites I've looked at.
|
||
|
|
||
|
#+begin_src shell :results silent
|
||
|
mkdir raw-data/external/2024-03-18-zoopla-rent-manc-listings/
|
||
|
#+end_src
|
||
|
|
||
|
[[file:./raw-data/external/2024-03-18-zoopla-rent-manc-listings/]]
|
||
|
|
||
|
#+begin_src lisp :results silent
|
||
|
(let ((counter 0))
|
||
|
(loop for file-path
|
||
|
in (directory #P"raw-data/external/2024-03-18-zoopla-rent-manc/*.html")
|
||
|
do (with-open-file (in-stream file-path)
|
||
|
(let* ((doc (plump:parse in-stream))
|
||
|
;; .listing-info
|
||
|
(listings (lquery:$ doc ".dkr2t82" (serialize))))
|
||
|
(loop for item across listings
|
||
|
do (let ((out-path
|
||
|
(merge-pathnames
|
||
|
#P"raw-data/external/2024-03-18-zoopla-rent-manc-listings/"
|
||
|
(format nil "listing-~a.html" (write-to-string counter)))))
|
||
|
(with-open-file (out-stream
|
||
|
out-path
|
||
|
:direction :output
|
||
|
:if-exists :supersede)
|
||
|
(format out-stream "~a~%" item))
|
||
|
(incf counter)))))))
|
||
|
#+end_src
|
||
|
|
||
|
#+begin_src shell :results output
|
||
|
COUNT=$(ls -1 "raw-data/external/2024-03-18-zoopla-rent-manc-listings/" | wc -l)
|
||
|
echo "There are $COUNT files in the directory."
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
: There are 14 files in the directory.
|
||
|
|
||
|
There are some listings which are student-only. Unfortunately, there doesn't
|
||
|
seem to be a filter for removing student-only listings. So, I'll need to remove
|
||
|
them manually at little later down the line. The total amount of listing is
|
||
|
small enough for it to not be a problem.
|
||
|
|
||
|
** Create CSV of Listings
|
||
|
|
||
|
#+begin_src lisp :results output raw
|
||
|
(let ((row-id 0)
|
||
|
(out-file #P"working-data/2024-03-18-zoopla-manc.csv"))
|
||
|
(with-open-file (out-stream
|
||
|
out-file
|
||
|
:direction :output
|
||
|
:if-exists :supersede)
|
||
|
(format out-stream "ROW-ID,RENT,LOCATION,URL,DESCRIPTION~%")
|
||
|
(loop for input
|
||
|
in (directory #P"raw-data/external/2024-03-18-zoopla-rent-manc-listings/*.html")
|
||
|
do (with-open-file (in-stream input)
|
||
|
(let* ((doc (plump:parse in-stream))
|
||
|
(price (lquery:$ doc "._64if862._194zg6t6" (text)))
|
||
|
(description (lquery:$ doc ".m6hnz63._194zg6t9" (text)))
|
||
|
;; e.g. Transform '£1,250 per month' to '1250'.
|
||
|
(cleaned-price
|
||
|
(first
|
||
|
(cl-ppcre:all-matches-as-strings
|
||
|
"\\d+"
|
||
|
(cl-ppcre:regex-replace-all
|
||
|
"[\\s\\n]+"
|
||
|
(format nil "~s" (str:replace-all "," "" (aref price 0)))
|
||
|
" "))))
|
||
|
(location (lquery:$ doc "address" (text)))
|
||
|
(link (lquery:$ doc "a" (attr :href))))
|
||
|
(format out-stream "~d,~d,~a,~a,~a~%"
|
||
|
row-id
|
||
|
cleaned-price
|
||
|
(str:replace-all "," " " (aref location 0))
|
||
|
(cl-ppcre:regex-replace "\\?search.*" (aref link 0) "")
|
||
|
(str:replace-all "," " " (aref description 0)))))
|
||
|
(incf row-id)))
|
||
|
(format t "[[file:./~a]]" out-file))
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
[[file:./working-data/2024-03-18-zoopla-manc.csv]]
|
||
|
|
||
|
*I've manually removed the student-only listing, since creating the CSV file.*
|
||
|
File is left with 5 listings.
|
||
|
|
||
|
* Explore CSV Data for Zoopla (2024-03-18)
|
||
|
|
||
|
#+begin_src lisp :session
|
||
|
(lisp-stat:defdf *zoop-man*
|
||
|
(lisp-stat:read-csv #P"working-data/2024-03-18-zoopla-manc.csv"))
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
: #<DATA-FRAME:DATA-FRAME (5 observations of 5 variables)>
|
||
|
|
||
|
The total amount of listings is tiny, so I can list them out here.
|
||
|
|
||
|
#+begin_src lisp :session :results output drawer
|
||
|
(lisp-stat:print-markdown *zoop-man*)
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
:results:
|
||
|
| ROW-ID | RENT | LOCATION | URL | DESCRIPTION |
|
||
|
|--------+------+--------------------------------------------------------+----------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||
|
| 2 | 1150 | Arches Whitworth Street West Manchester M1 | https://www.zoopla.co.uk/to-rent/details/65630906/ | **3 month agreements only - available 23rd March** A true Manchester gem minutes from the Northern Quarter Canal Street and Piccadilly. This is ... |
|
||
|
| 3 | 1150 | Water Street Manchester M3 | https://www.zoopla.co.uk/to-rent/details/66713818/ | 1-6 months Tenancy bills included no couples due to regulations**Perfect for Contractors & Work Teams****Perfect for Business ... |
|
||
|
| 5 | 1250 | Arches Whitworth Street West Manchester M1 | https://www.zoopla.co.uk/to-rent/details/66338812/ | *3 month agreements only - available 23rd March* A true Manchester gem minutes from the Northern Quarter Canal Street and Piccadilly. This is ... |
|
||
|
| 8 | 1050 | Little Lever Street Manchester Greater Manchester M1 | https://www.zoopla.co.uk/to-rent/details/65042494/ | ** available from 19/04/2024 ** ** all bills inclusive in rent ** Reeds Rains are delighted to market this fantastic furnished studio apartment ... |
|
||
|
| 11 | 1089 | Cross Street Manchester M2 | https://www.zoopla.co.uk/to-rent/details/66673111/ | Listing code 692841. Our contemporary studio apartments at 25 Cross Street are thoughtfully designed to a high standard.Featuring a comfy double ... |
|
||
|
:end:
|
||
|
|
||
|
#+begin_src lisp :session :results file
|
||
|
(vega:defplot zoopla-monthly-manc
|
||
|
`(:title "Rent Rates for Manchester on Zoople (18/03/2024)"
|
||
|
:width 600
|
||
|
:height 600
|
||
|
:data ,*zoop-man*
|
||
|
:layer #((:mark (:type :bar)
|
||
|
:encoding (:x (:field :row-id :title "Assigned Id." :type :nominal :axis ("labelAngle" 0))
|
||
|
:y (:field :rent :title "Monthly Rent with Bills Inc. (£)" :type :quantitative)
|
||
|
:tooltip (:field :rent)))
|
||
|
(:mark (:type rule :color "darkorange" :size 3)
|
||
|
:encoding (:y (:field :rent :type :quantitative :aggregate :average)
|
||
|
:tooltip (:field :rent :type :quantitative :aggregate :average))))))
|
||
|
(vega:write-html zoopla-monthly-manc "renders/2024-03-18-zoopla-rent-manc.html")
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
[[file:renders/2024-03-18-zoopla-rent-manc.html]]
|
||
|
|
||
|
#+begin_src shell :results silent
|
||
|
mv ~/Downloads/visualization.png ./renders/2024-03-18-zoopla-rent-manc.png
|
||
|
#+end_src
|
||
|
|
||
|
[[file:./renders/2024-03-18-zoopla-rent-manc.png]]
|
||
|
|
||
|
#+begin_src lisp :session :results output code raw
|
||
|
(format t "- Mean Rent: £ ~a~%" (float (lisp-stat:mean *zoop-man*:rent)))
|
||
|
(format t "- Min. Rent: £ ~d~%" (reduce #'min *zoop-man*:rent))
|
||
|
(format t "- Max. Rent: £ ~d" (reduce #'max *zoop-man*:rent))
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
- Mean Rent: £ 1137.8
|
||
|
- Min. Rent: £ 1050
|
||
|
- Max. Rent: £ 1250
|
||
|
|
||
|
#+begin_src calc :results output
|
||
|
1137.8 * 12
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
: 13653.6
|
||
|
|
||
|
* Summary of Zoopla
|
||
|
|
||
|
Based on the average rent for Zoopla, I would need to make around £14,000/yr to
|
||
|
cover my living costs. This doesn't include travel, food, clothing, socialising
|
||
|
or Income Tax Payments.
|
||
|
|
||
|
Most listing were aimed at students and the number of listings was tiny,
|
||
|
especially when compared to [[file:./spare-room-manchester.org][Spareroom]] and [[file:./open-rent-manchester.org][Open Rent]].
|
||
|
|
||
|
To get an idea of what I need for ’survival mode’, I'll add £5,000 onto the
|
||
|
total above and apply the usual calculations, established in the other files.
|
||
|
|
||
|
#+begin_src calc :results output
|
||
|
13653.6 + 5000
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
: 18653.6
|
||
|
|
||
|
Using £18,653.6 as the starting point (see [[file:./uk-wage-tax.org][UK Wage and Tax Rated]],
|
||
|
|
||
|
#+begin_src lisp :results output raw
|
||
|
(let* ((earning-target 18653.6)
|
||
|
(p-allow 12570)
|
||
|
(taxable-income (- earning-target p-allow))
|
||
|
(tax-to-pay (* taxable-income 0.2))
|
||
|
(total (- earning-target tax-to-pay)))
|
||
|
(format t "- Annual Target Salary: £~a~%" earning-target)
|
||
|
(format t "- Part of Salary which is Taxable: £~a~%" taxable-income)
|
||
|
(format t "- Tax to Pay: £~a~%" tax-to-pay)
|
||
|
(format t "- Salary After Tax: £~a~%" total))
|
||
|
#+end_src
|
||
|
|
||
|
#+RESULTS:
|
||
|
- Annual Target Salary: £18653.6
|
||
|
- Part of Salary which is Taxable: £6083.5996
|
||
|
- Tax to Pay: £1216.72
|
||
|
- Salary After Tax: £17436.879
|
||
|
|
||
|
| Time Span | Value After Tax (£) | Mean Rent (£) |
|
||
|
|-----------------------+---------------------+---------------|
|
||
|
| Annually | 17436.88 | 1137.8 |
|
||
|
| Monthly (Before Rent) | 1453.0733 | |
|
||
|
| Monthly (After Rent) | 315.2733 | |
|
||
|
| Weekly (After Rent) | 78.818325 | |
|
||
|
| Daily (After Rent) | 11.259761 | |
|
||
|
#+TBLFM: @3$2=@-1/12::@4$2=@-1-@-2$+1::@5$2=@-1/4::@6$2=@-1/7
|
||
|
|
||
|
So, the spending limit is £11.26/day.
|