mirror of
https://github.com/rjNemo/copro-api
synced 2026-06-06 02:16:44 +00:00
2h
This commit is contained in:
commit
35816b96c2
21 changed files with 2230 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
**/api/migrations
|
||||||
|
**/api/data/.ipynb_checkpoints
|
||||||
|
**/api/data/*csv
|
||||||
|
db*
|
||||||
17
Pipfile
Normal file
17
Pipfile
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
pandas = "*"
|
||||||
|
djangorestframework = "*"
|
||||||
|
django = "*"
|
||||||
|
sklearn = "*"
|
||||||
|
numpy = "*"
|
||||||
|
joblib = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.7"
|
||||||
190
Pipfile.lock
generated
Normal file
190
Pipfile.lock
generated
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "b61f243cbc1d68a628203e9bf1210e97d98d5e74516535f2cb22fccf2471043b"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.7"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"asgiref": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
|
||||||
|
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
|
||||||
|
],
|
||||||
|
"version": "==3.2.10"
|
||||||
|
},
|
||||||
|
"django": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:31a5fbbea5fc71c99e288ec0b2f00302a0a92c44b13ede80b73a6a4d6d205582",
|
||||||
|
"sha256:5457fc953ec560c5521b41fad9e6734a4668b7ba205832191bbdff40ec61073c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.0.8"
|
||||||
|
},
|
||||||
|
"djangorestframework": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
|
||||||
|
"sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.11.0"
|
||||||
|
},
|
||||||
|
"joblib": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8f52bf24c64b608bf0b2563e0e47d6fcf516abc8cfafe10cfd98ad66d94f92d6",
|
||||||
|
"sha256:d348c5d4ae31496b2aa060d6d9b787864dd204f9480baaa52d18850cb43e9f49"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.16.0"
|
||||||
|
},
|
||||||
|
"numpy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:082f8d4dd69b6b688f64f509b91d482362124986d98dc7dc5f5e9f9b9c3bb983",
|
||||||
|
"sha256:1bc0145999e8cb8aed9d4e65dd8b139adf1919e521177f198529687dbf613065",
|
||||||
|
"sha256:309cbcfaa103fc9a33ec16d2d62569d541b79f828c382556ff072442226d1968",
|
||||||
|
"sha256:3673c8b2b29077f1b7b3a848794f8e11f401ba0b71c49fbd26fb40b71788b132",
|
||||||
|
"sha256:480fdd4dbda4dd6b638d3863da3be82873bba6d32d1fc12ea1b8486ac7b8d129",
|
||||||
|
"sha256:56ef7f56470c24bb67fb43dae442e946a6ce172f97c69f8d067ff8550cf782ff",
|
||||||
|
"sha256:5a936fd51049541d86ccdeef2833cc89a18e4d3808fe58a8abeb802665c5af93",
|
||||||
|
"sha256:5b6885c12784a27e957294b60f97e8b5b4174c7504665333c5e94fbf41ae5d6a",
|
||||||
|
"sha256:667c07063940e934287993366ad5f56766bc009017b4a0fe91dbd07960d0aba7",
|
||||||
|
"sha256:7ed448ff4eaffeb01094959b19cbaf998ecdee9ef9932381420d514e446601cd",
|
||||||
|
"sha256:8343bf67c72e09cfabfab55ad4a43ce3f6bf6e6ced7acf70f45ded9ebb425055",
|
||||||
|
"sha256:92feb989b47f83ebef246adabc7ff3b9a59ac30601c3f6819f8913458610bdcc",
|
||||||
|
"sha256:935c27ae2760c21cd7354402546f6be21d3d0c806fffe967f745d5f2de5005a7",
|
||||||
|
"sha256:aaf42a04b472d12515debc621c31cf16c215e332242e7a9f56403d814c744624",
|
||||||
|
"sha256:b12e639378c741add21fbffd16ba5ad25c0a1a17cf2b6fe4288feeb65144f35b",
|
||||||
|
"sha256:b1cca51512299841bf69add3b75361779962f9cee7d9ee3bb446d5982e925b69",
|
||||||
|
"sha256:b8456987b637232602ceb4d663cb34106f7eb780e247d51a260b84760fd8f491",
|
||||||
|
"sha256:b9792b0ac0130b277536ab8944e7b754c69560dac0415dd4b2dbd16b902c8954",
|
||||||
|
"sha256:c9591886fc9cbe5532d5df85cb8e0cc3b44ba8ce4367bd4cf1b93dc19713da72",
|
||||||
|
"sha256:cf1347450c0b7644ea142712619533553f02ef23f92f781312f6a3553d031fc7",
|
||||||
|
"sha256:de8b4a9b56255797cbddb93281ed92acbc510fb7b15df3f01bd28f46ebc4edae",
|
||||||
|
"sha256:e1b1dc0372f530f26a03578ac75d5e51b3868b9b76cd2facba4c9ee0eb252ab1",
|
||||||
|
"sha256:e45f8e981a0ab47103181773cc0a54e650b2aef8c7b6cd07405d0fa8d869444a",
|
||||||
|
"sha256:e4f6d3c53911a9d103d8ec9518190e52a8b945bab021745af4939cfc7c0d4a9e",
|
||||||
|
"sha256:ed8a311493cf5480a2ebc597d1e177231984c818a86875126cfd004241a73c3e",
|
||||||
|
"sha256:ef71a1d4fd4858596ae80ad1ec76404ad29701f8ca7cdcebc50300178db14dfc"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.19.1"
|
||||||
|
},
|
||||||
|
"pandas": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0210f8fe19c2667a3817adb6de2c4fd92b1b78e1975ca60c0efa908e0985cbdb",
|
||||||
|
"sha256:0227e3a6e3a22c0e283a5041f1e3064d78fbde811217668bb966ed05386d8a7e",
|
||||||
|
"sha256:0bc440493cf9dc5b36d5d46bbd5508f6547ba68b02a28234cd8e81fdce42744d",
|
||||||
|
"sha256:16504f915f1ae424052f1e9b7cd2d01786f098fbb00fa4e0f69d42b22952d798",
|
||||||
|
"sha256:182a5aeae319df391c3df4740bb17d5300dcd78034b17732c12e62e6dd79e4a4",
|
||||||
|
"sha256:35db623487f00d9392d8af44a24516d6cb9f274afaf73cfcfe180b9c54e007d2",
|
||||||
|
"sha256:40ec0a7f611a3d00d3c666c4cceb9aa3f5bf9fbd81392948a93663064f527203",
|
||||||
|
"sha256:47a03bfef80d6812c91ed6fae43f04f2fa80a4e1b82b35aa4d9002e39529e0b8",
|
||||||
|
"sha256:4b21d46728f8a6be537716035b445e7ef3a75dbd30bd31aa1b251323219d853e",
|
||||||
|
"sha256:4d1a806252001c5db7caecbe1a26e49a6c23421d85a700960f6ba093112f54a1",
|
||||||
|
"sha256:60e20a4ab4d4fec253557d0fc9a4e4095c37b664f78c72af24860c8adcd07088",
|
||||||
|
"sha256:9f61cca5262840ff46ef857d4f5f65679b82188709d0e5e086a9123791f721c8",
|
||||||
|
"sha256:a15835c8409d5edc50b4af93be3377b5dd3eb53517e7f785060df1f06f6da0e2",
|
||||||
|
"sha256:b39508562ad0bb3f384b0db24da7d68a2608b9ddc85b1d931ccaaa92d5e45273",
|
||||||
|
"sha256:ed60848caadeacecefd0b1de81b91beff23960032cded0ac1449242b506a3b3f",
|
||||||
|
"sha256:fc714895b6de6803ac9f661abb316853d0cd657f5d23985222255ad76ccedc25"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.1.0"
|
||||||
|
},
|
||||||
|
"python-dateutil": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||||
|
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||||
|
],
|
||||||
|
"version": "==2.8.1"
|
||||||
|
},
|
||||||
|
"pytz": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
|
||||||
|
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
|
||||||
|
],
|
||||||
|
"version": "==2020.1"
|
||||||
|
},
|
||||||
|
"scikit-learn": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:04799686060ecbf8992f26a35be1d99e981894c8c7860c1365cda4200f954a16",
|
||||||
|
"sha256:058d213092de4384710137af1300ed0ff030b8c40459a6c6f73c31ccd274cc39",
|
||||||
|
"sha256:0c3464e46ef8bd4f1bfa5c009648c6449412c8f7e9b3fc0c9e3d800139c48827",
|
||||||
|
"sha256:0e7b55f73b35537ecd0d19df29dd39aa9e076dba78f3507b8136c819d84611fd",
|
||||||
|
"sha256:16feae4361be6b299d4d08df5a30956b4bfc8eadf173fe9258f6d59630f851d4",
|
||||||
|
"sha256:244ca85d6eba17a1e6e8a66ab2f584be6a7784b5f59297e3d7ff8c7983af627c",
|
||||||
|
"sha256:3e6e92b495eee193a8fa12a230c9b7976ea0fc1263719338e35c986ea1e42cff",
|
||||||
|
"sha256:5bcea4d6ee431c814261117281363208408aa4e665633655895feb059021aca6",
|
||||||
|
"sha256:93f56abd316d131645559ec0ab4f45e3391c2ccdd4eadaa4912f4c1e0a6f2c96",
|
||||||
|
"sha256:9e04c0811ea92931ee8490d638171b8cb2f21387efcfff526bbc8c2a3da60f1c",
|
||||||
|
"sha256:bded94236e16774385202cafd26190ce96db18e4dc21e99473848c61e4fdc400",
|
||||||
|
"sha256:c2fa33d20408b513cf432505c80e6eb4bf4d71434f1ae36680765d4a2c2a16ec",
|
||||||
|
"sha256:e3fec1c8831f8f93ad85581ca29ca1bb88e2da377fb097cf8322aa89c21bc9b8",
|
||||||
|
"sha256:e585682e37f2faa81ad6cd4472fff646bf2fd0542147bec93697a905db8e6bd2",
|
||||||
|
"sha256:e9879ba9e64ec3add41bf201e06034162f853652ef4849b361d73b0deb3153ad",
|
||||||
|
"sha256:ebe853e6f318f9d8b3b74dd17e553720d35646eff675a69eeaed12fbbbb07daa"
|
||||||
|
],
|
||||||
|
"version": "==0.23.1"
|
||||||
|
},
|
||||||
|
"scipy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:066c513d90eb3fd7567a9e150828d39111ebd88d3e924cdfc9f8ce19ab6f90c9",
|
||||||
|
"sha256:07e52b316b40a4f001667d1ad4eb5f2318738de34597bd91537851365b6c61f1",
|
||||||
|
"sha256:0a0e9a4e58a4734c2eba917f834b25b7e3b6dc333901ce7784fd31aefbd37b2f",
|
||||||
|
"sha256:1c7564a4810c1cd77fcdee7fa726d7d39d4e2695ad252d7c86c3ea9d85b7fb8f",
|
||||||
|
"sha256:315aa2165aca31375f4e26c230188db192ed901761390be908c9b21d8b07df62",
|
||||||
|
"sha256:6e86c873fe1335d88b7a4bfa09d021f27a9e753758fd75f3f92d714aa4093768",
|
||||||
|
"sha256:8e28e74b97fc8d6aa0454989db3b5d36fc27e69cef39a7ee5eaf8174ca1123cb",
|
||||||
|
"sha256:92eb04041d371fea828858e4fff182453c25ae3eaa8782d9b6c32b25857d23bc",
|
||||||
|
"sha256:a0afbb967fd2c98efad5f4c24439a640d39463282040a88e8e928db647d8ac3d",
|
||||||
|
"sha256:a785409c0fa51764766840185a34f96a0a93527a0ff0230484d33a8ed085c8f8",
|
||||||
|
"sha256:cca9fce15109a36a0a9f9cfc64f870f1c140cb235ddf27fe0328e6afb44dfed0",
|
||||||
|
"sha256:d56b10d8ed72ec1be76bf10508446df60954f08a41c2d40778bc29a3a9ad9bce",
|
||||||
|
"sha256:dac09281a0eacd59974e24525a3bc90fa39b4e95177e638a31b14db60d3fa806",
|
||||||
|
"sha256:ec5fe57e46828d034775b00cd625c4a7b5c7d2e354c3b258d820c6c72212a6ec",
|
||||||
|
"sha256:eecf40fa87eeda53e8e11d265ff2254729d04000cd40bae648e76ff268885d66",
|
||||||
|
"sha256:fc98f3eac993b9bfdd392e675dfe19850cc8c7246a8fd2b42443e506344be7d9"
|
||||||
|
],
|
||||||
|
"version": "==1.5.2"
|
||||||
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||||
|
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||||
|
],
|
||||||
|
"version": "==1.15.0"
|
||||||
|
},
|
||||||
|
"sklearn": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:e23001573aa194b834122d2b9562459bf5ae494a2d59ca6b8aa22c85a44c0e31"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.0"
|
||||||
|
},
|
||||||
|
"sqlparse": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
|
||||||
|
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
|
||||||
|
],
|
||||||
|
"version": "==0.3.1"
|
||||||
|
},
|
||||||
|
"threadpoolctl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:38b74ca20ff3bb42caca8b00055111d74159ee95c4370882bbff2b93d24da725",
|
||||||
|
"sha256:ddc57c96a38beb63db45d6c159b5ab07b6bced12c45a1f07b2b92f272aebfa6b"
|
||||||
|
],
|
||||||
|
"version": "==2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
25
README.md
Normal file
25
README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Exercice dev python
|
||||||
|
|
||||||
|
MeilleureCopro souhaite développer une API de statistiques sur des données d’annonces immobilières pour des besoins internes.
|
||||||
|
|
||||||
|
Afin de valider cette demande, votre objectif est de construire en un minimum de temps une API qui répondra aux besoins suivants des utilisateurs internes MeilleureCopro :
|
||||||
|
|
||||||
|
- L'utilisateur doit pouvoir connaître les charges de copropriétés moyennes, les quantiles 10%, 90% sur un département, une ville ou code postal
|
||||||
|
- L'utilisateur interne doit pouvoir très simplement interroger la base de données via un navigateur
|
||||||
|
- L'utilisateur doit pouvoir rentrer les informations suivantes sur un appartement au sein d'une copropriété :
|
||||||
|
|
||||||
|
- une adresse,
|
||||||
|
- la surface de l'appartement,
|
||||||
|
- la présence d'ascenseur,
|
||||||
|
- la présence de chauffage collectif
|
||||||
|
|
||||||
|
et obtenir une estimation des charges de l'appartement. Vous utiliserez un modèle pour faire cette estimation.
|
||||||
|
|
||||||
|
Voici le dataset d’annonces immobilières sur lequel vous calculerez les statistiques demandées : [dataset](https://storage.googleapis.com/data.meilleurecopro.com/stage/dataset_annonces.csv.tar.gz).
|
||||||
|
|
||||||
|
## Outils choisis
|
||||||
|
|
||||||
|
- pandas
|
||||||
|
- scikit-learn
|
||||||
|
- Django
|
||||||
|
- Django REST Framework
|
||||||
0
api/__init__.py
Normal file
0
api/__init__.py
Normal file
3
api/admin.py
Normal file
3
api/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
5
api/apps.py
Normal file
5
api/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
name = 'api'
|
||||||
1568
api/data/model.ipynb
Normal file
1568
api/data/model.ipynb
Normal file
File diff suppressed because it is too large
Load diff
83
api/data/prediction.py
Normal file
83
api/data/prediction.py
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from sklearn.impute import SimpleImputer
|
||||||
|
from sklearn.linear_model import LinearRegression
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
# TODO: use one hot encoding to handle unknown values in transform step
|
||||||
|
from sklearn.preprocessing import LabelEncoder
|
||||||
|
|
||||||
|
from .statistics import data_path
|
||||||
|
|
||||||
|
|
||||||
|
class LinearRegressor:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.X, self.y = self.preprocessing()
|
||||||
|
|
||||||
|
def preprocessing(self):
|
||||||
|
"""Prepare data for modelling."""
|
||||||
|
|
||||||
|
# read csv
|
||||||
|
raw_data = pd.read_csv(
|
||||||
|
data_path, dtype={'DEPT_CODE': str, 'ZIP_CODE': str, 'INSEE_CODE': str})
|
||||||
|
|
||||||
|
# drop missing values from significative columns
|
||||||
|
feature_cols = ['CONDOMINIUM_EXPENSES',
|
||||||
|
'DEPT_CODE', 'ZIP_CODE', 'CITY', 'SURFACE']
|
||||||
|
data = raw_data.dropna(subset=feature_cols)
|
||||||
|
|
||||||
|
# columns used in modelling
|
||||||
|
cols = ['ZIP_CODE', 'DEPT_CODE', 'CITY', 'INSEE_CODE', 'LATITUDE',
|
||||||
|
'LONGITUDE', 'SURFACE', 'HEATING_MODE', 'ELEVATOR']
|
||||||
|
|
||||||
|
# target
|
||||||
|
y = data['CONDOMINIUM_EXPENSES']
|
||||||
|
# features
|
||||||
|
X = data[cols]
|
||||||
|
|
||||||
|
# surface rows are not correctly typed I do it manually
|
||||||
|
X['SURFACE'] = X['SURFACE'].astype(float)
|
||||||
|
|
||||||
|
# split dataset for training
|
||||||
|
X_train, X_val, y_train, y_val = train_test_split(
|
||||||
|
X, y, train_size=0.7, random_state=0)
|
||||||
|
|
||||||
|
# fill missing values with most frequent values. TODO: suboptimal method
|
||||||
|
train_mode = dict(X_train.mode().iloc[0])
|
||||||
|
X_train = X_train.fillna(train_mode)
|
||||||
|
|
||||||
|
cols = X_train.dtypes == 'object'
|
||||||
|
cat_cols = list(cols[cols].index)
|
||||||
|
cols = X_train.dtypes != 'object'
|
||||||
|
num_cols = list(cols[cols].index)
|
||||||
|
|
||||||
|
# handle missing values on categorical columns
|
||||||
|
imputer = SimpleImputer(strategy='constant')
|
||||||
|
X_train[cat_cols] = imputer.fit_transform(X_train[cat_cols])
|
||||||
|
X_val[cat_cols] = imputer.transform(X_val[cat_cols])
|
||||||
|
|
||||||
|
# encode categorical data
|
||||||
|
encoder = LabelEncoder()
|
||||||
|
|
||||||
|
for col in cat_cols:
|
||||||
|
X_train[col] = encoder.fit_transform(X_train[col])
|
||||||
|
|
||||||
|
# handle numerical missing values
|
||||||
|
num_imputer = SimpleImputer(strategy='mean')
|
||||||
|
|
||||||
|
X_train[num_cols] = num_imputer.fit_transform(X_train[num_cols])
|
||||||
|
X_val[num_cols] = num_imputer.transform(X_val[num_cols])
|
||||||
|
|
||||||
|
return X_train, y_train
|
||||||
|
|
||||||
|
def get_trained_model(self):
|
||||||
|
lin_reg = LinearRegression().fit(self.X, self.y)
|
||||||
|
|
||||||
|
return lin_reg
|
||||||
|
|
||||||
|
def predict_expenses(self, data):
|
||||||
|
"""Return condominium expenses as a function of data."""
|
||||||
|
|
||||||
|
model = self.get_trained_model()
|
||||||
|
|
||||||
|
return model.predict(data)
|
||||||
65
api/data/statistics.py
Normal file
65
api/data/statistics.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from copro.settings import BASE_DIR
|
||||||
|
|
||||||
|
# TODO: put in env var and import it
|
||||||
|
data_path = os.path.join(BASE_DIR, 'api/data/annonces.csv')
|
||||||
|
|
||||||
|
|
||||||
|
def get_data_from_csv(path):
|
||||||
|
"""
|
||||||
|
Create dataframe from csv filepath. Keep only needed columns
|
||||||
|
|
||||||
|
`path` is the csv file location.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# import data as dataframe
|
||||||
|
raw_data = pd.read_csv(path)
|
||||||
|
|
||||||
|
# keep only useful columns
|
||||||
|
feature_cols = ['CONDOMINIUM_EXPENSES', 'DEPT_CODE', 'ZIP_CODE', 'CITY']
|
||||||
|
data = raw_data.dropna(subset=feature_cols)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_condo_expenses_by(col, value):
|
||||||
|
"""
|
||||||
|
Return mean, 1st and 9th decile condominium expenses values for the given query type.
|
||||||
|
|
||||||
|
`Col` can either be: `DEPT_CODE`, `CITY` or `ZIP_CODE`.
|
||||||
|
|
||||||
|
`Value` is the actual query parameter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert col in ('DEPT_CODE', 'CITY',
|
||||||
|
'ZIP_CODE'), "col must be 'dept', 'city' or 'zip'"
|
||||||
|
|
||||||
|
data = get_data_from_csv(data_path)
|
||||||
|
|
||||||
|
# group data by column and compute statistics
|
||||||
|
group = data.groupby(col, as_index=False)['CONDOMINIUM_EXPENSES']
|
||||||
|
mean = group.mean()
|
||||||
|
first_quantile = group.quantile(0.1)
|
||||||
|
ninth_quantile = group.quantile(0.9)
|
||||||
|
|
||||||
|
# build filtering condition
|
||||||
|
if col == 'DEPT_CODE':
|
||||||
|
condition = mean['DEPT_CODE'] == value
|
||||||
|
elif col == 'CITY':
|
||||||
|
condition = mean['CITY'] == value
|
||||||
|
else:
|
||||||
|
condition = mean['ZIP_CODE'] == value
|
||||||
|
|
||||||
|
# TODO: refactor
|
||||||
|
mean = mean['CONDOMINIUM_EXPENSES'][condition].iloc[0]
|
||||||
|
first_quantile = first_quantile['CONDOMINIUM_EXPENSES'][condition].iloc[0]
|
||||||
|
ninth_quantile = ninth_quantile['CONDOMINIUM_EXPENSES'][condition].iloc[0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"mean": mean,
|
||||||
|
"1st_quantile": first_quantile,
|
||||||
|
"9th_quantile": ninth_quantile,
|
||||||
|
}
|
||||||
18
api/models.py
Normal file
18
api/models.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class CondominiumExpense(models.Model):
|
||||||
|
mean = models.FloatField()
|
||||||
|
first_quantile = models.FloatField()
|
||||||
|
ninth_quantile = models.FloatField()
|
||||||
|
|
||||||
|
|
||||||
|
class CondominiumExpenseQuery(models.Model):
|
||||||
|
QUERY_TYPES = [
|
||||||
|
('DEPT_CODE', 'Department'),
|
||||||
|
('ZIP_CODE', 'Postal Code'),
|
||||||
|
('CITY', 'City'),
|
||||||
|
]
|
||||||
|
query_type = models.CharField(
|
||||||
|
max_length=20, choices=QUERY_TYPES, default='DEPT_CODE')
|
||||||
|
value = models.CharField(max_length=40, blank=True, default="")
|
||||||
15
api/serializers.py
Normal file
15
api/serializers.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import CondominiumExpense, CondominiumExpenseQuery
|
||||||
|
|
||||||
|
|
||||||
|
class CondominiumSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CondominiumExpense
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class CondominiumQuerySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CondominiumExpenseQuery
|
||||||
|
fields = '__all__'
|
||||||
3
api/tests.py
Normal file
3
api/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
8
api/urls.py
Normal file
8
api/urls.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import get_condominium_expenses, make_expenses_prediction
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('api/v1/', get_condominium_expenses),
|
||||||
|
path('api/v1/predict/', make_expenses_prediction),
|
||||||
|
]
|
||||||
29
api/views.py
Normal file
29
api/views.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from .data.prediction import LinearRegressor
|
||||||
|
from .data.statistics import get_condo_expenses_by
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET', 'POST'])
|
||||||
|
def get_condominium_expenses(request):
|
||||||
|
"""TODO: refactor using APIView class"""
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
if request.method == "POST":
|
||||||
|
query_type = request.data["query_type"]
|
||||||
|
query_val = request.data['query_val']
|
||||||
|
|
||||||
|
data = get_condo_expenses_by(query_type, query_val)
|
||||||
|
|
||||||
|
return Response(data)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def make_expenses_prediction(request):
|
||||||
|
"""Return condominium expenses prediction."""
|
||||||
|
|
||||||
|
algo = LinearRegressor
|
||||||
|
prediction = algo.predict_expenses(request.data)
|
||||||
|
|
||||||
|
return Response(prediction)
|
||||||
0
copro/__init__.py
Normal file
0
copro/__init__.py
Normal file
16
copro/asgi.py
Normal file
16
copro/asgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
ASGI config for copro project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'copro.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
122
copro/settings.py
Normal file
122
copro/settings.py
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
"""
|
||||||
|
Django settings for copro project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.0.4.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '6)akoq-jb7%!ydxtji%ss+xk7o&@e-&=eep(4r2%3zz^^&4wzv'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'rest_framework',
|
||||||
|
'api.apps.ApiConfig'
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'copro.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'copro.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
22
copro/urls.py
Normal file
22
copro/urls.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""copro URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('', include('api.urls'))
|
||||||
|
]
|
||||||
16
copro/wsgi.py
Normal file
16
copro/wsgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
WSGI config for copro project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'copro.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
21
manage.py
Executable file
21
manage.py
Executable file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'copro.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Loading…
Reference in a new issue