diff --git a/zoopla-manchester.org b/zoopla-manchester.org new file mode 100644 index 0000000..8dcdb7c --- /dev/null +++ b/zoopla-manchester.org @@ -0,0 +1,257 @@ +#+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: +: # + +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.