phplyr

Rent dashboard

This page uses the simulated rentals data. It does not describe real rentals.

This example is a small reactive dashboard in the style of a Shiny app. Each input is a property of a Livewire component. When an input changes, the server filters the data with phplyr, summarises it, refits a logistic model with phit, and returns new tables and phiz charts. The page has no JavaScript written for it.

The model estimates the probability that a rental costs more than the budget from its floor area and number of bedrooms.

Code

class RentDashboard extends Component
{
    public array $neighborhoods = [...];
    public int $minBedrooms = 1;
    public int $maxBedrooms = 4;
    public int $budget = 2500;

    public function render()
    {
        $rentals = DataFrame::fromQuery(SimulatedRental::query()) |>
            filter(col('neighborhood')->isIn($this->neighborhoods),
                      col('bedrooms')->between($this->minBedrooms, $this->maxBedrooms)) |>
            mutate(over_budget: col('monthly_rent')->gt($this->budget));

        $summary = $rentals |>
            group_by('neighborhood') |>
            summarise(rentals: n(), median_rent: median('monthly_rent'),
                         share_over_budget: mean('over_budget'));

        $model = $rentals |> glm('over_budget ~ area_m2 + bedrooms', family: 'binomial');

        return view('livewire.rent-dashboard', compact('rentals', 'summary', 'model'));
    }
}
class RentDashboard extends Component
{
    public array $neighborhoods = [...];
    public int $minBedrooms = 1;
    public int $maxBedrooms = 4;
    public int $budget = 2500;

    public function render()
    {
        $rentals = phplyr(DataFrame::fromQuery(SimulatedRental::query()))
            ->filter(col('neighborhood')->isIn($this->neighborhoods),
                     col('bedrooms')->between($this->minBedrooms, $this->maxBedrooms))
            ->mutate(over_budget: col('monthly_rent')->gt($this->budget))
            ->get();

        $summary = phplyr($rentals)
            ->groupBy('neighborhood')
            ->summarise(rentals: n(), median_rent: median('monthly_rent'),
                        share_over_budget: mean('over_budget'))
            ->get();

        $model = phplyr($rentals)->glm('over_budget ~ area_m2 + bedrooms', family: 'binomial');

        return view('livewire.rent-dashboard', compact('rentals', 'summary', 'model'));
    }
}
<input type="checkbox" value="Harbor" wire:model.live="neighborhoods">
<x-phiz :plot="$scatter" />

By neighborhood

Neighborhood Rentals Median rent Mean area (m²) Over 2,500
Harbor 507 2,711 84.0 59.6%
Hillcrest 476 2,465 85.3 48.9%
Northgate 537 2,132 84.3 32.0%
Old Town 501 2,592 85.5 54.7%
Riverside 455 2,151 82.1 32.3%
University 524 2,291 82.8 40.6%

Rent and area

Chance of exceeding the budget

What if

Predicted rent
2,000
95% prediction interval 1,704 to 2,296
Chance the rent is over 2,500
9.6%
95% confidence interval 7.8% to 11.6%

Phit\predict() predicts both values for one 75 m², 2-bedroom rental in Riverside, using models fitted to the selected rentals. The rent comes from the linear model monthly_rent ~ area_m2 + bedrooms + neighborhood. Its prediction interval covers a single rental's rent, so it is wider than an interval for the average rent. The chance of exceeding the budget comes from the logistic model below, and its interval is computed on the log-odds scale.

Logistic model: over_budget ~ area_m2 + bedrooms

Term Log odds Std. error Odds ratio
(Intercept) -13.233 0.544
area_m2 0.124 0.007 1.132
bedrooms 0.860 0.131 2.363

n = 3,000, residual deviance = 1,376.5 against a null deviance of 4,125.1, AIC = 1,382.5.