第9章 智能租房——详情页

学习目标

  • 掌握详情页房源数据展示功能的逻辑,能够实现在详情页上展示基本信息和配套设施

  • 了解数据可视化,能够说出数据可视化的概念和流程

  • 熟悉ECharts的用法和配置项,能够通过ECharts绘制常用图表,并为图表添加配置项

  • 掌握户型占比可视化功能的逻辑,能够实现户型占比可视化功能

  • 掌握小区房源数量TOP20可视化的逻辑,能够实现小区房源数量TOP20可视化的功能

  • 掌握户型价格走势可视化的逻辑,能够实现户型价格走势可视化的功能

  • 了解线性回归算法,能够说出线性回归算法的概念与应用

  • 熟悉scikit-learn库的用法,能够通过scikit-learn库实现线性回归算法

  • 掌握预测房价走势可视化的逻辑,能够实现预测房价走势可视化的功能

详情页是智能租房项目中比较重要的模块,它除了比列表页展示更为详尽的房源信息外,还以图表的形式为用户展示了户型、数量、价格走势、预测房价走势等相关信息。详情页模块包含5个功能,分别是房源信息展示、户型占比可视化、小区房源数量TOP20可视化、户型价格走势可视化和预测房价走势可视化,本章将带领大家实现这5个功能。

9.1 详情页房源数据展示

9.1.1 房源基本信息展示

详情页左侧的房源基本信息如图所示。 页面展示的房源信息包括标题、图片、价格、收藏、基本属性和房屋卖点,其中基本属性又包括房屋户型、所在区域、建筑面积、租住类型、房屋朝向和房东电话,房屋卖点又包括交通条件和优势条件。

房源基本信息展示实现房源基本信息展示的功能。

1.接口设计

为了确保从首页或列表页能跳转到正确的详情页,需要提前明确详情页的接口信息。

接口描述说明
请求页面detail_page.html
请求方式GET
请求地址/house/int:hid
返回数据房源对象

2.后端实现

基本信息展示的后端逻辑:首先获取前端请求的房源ID,然后通过SQLALchemy从数据库中查询该房源ID对应的房源对象,并将该房源对象的基本信息渲染到前端页面中。

房源基本信息展示功能具体步骤: (1)在house项目的根目录下app.py文件,该文件用于存放有关详情页逻辑的代码。

(2)定义一个视图函数detail(),用于根据URL地址中的房源ID获取相应的房源对象,之后将该房源对象的基本信息进行模板渲染。

# 实现房源数据展示
@app.route('/house/<int:hid>')
def detail(hid):
    sql="select * from house_info where id=%s"
    cursor.execute(sql,(hid,))
    # 从数据库查询房源ID为hid的房源对象
    house = cursor.fetchall()[0]
    return render_template('detail_page.html', house=house)

(3)在详情页的基本信息中,有的交通条件对应的数据为空,因此这里将使用模板过滤器对这些空数据进行处理。在app.py文件中增加一个过滤器函数deal_traffic_txt(),通过该函数处理交通条件有无数据的情况,并将该过滤器添加到蓝图中。

# 自定义过滤器,用于处理交通条件有无数据的情况
def deal_traffic_txt(word):
    if len(word) == 0 or word is None:
        return '暂无信息!'
    else:
        return word

app.add_template_filter(deal_traffic_txt, 'dealNone')

3.渲染模板

后端代码将当前房源信息放在上下文字典中传递给模板文件detail_page.html,模板文件可以通过house获取当前房源的信息。在detail_page.html文件中,查询class属性值为row info-line的div标签,将该标签内部有关房源基本信息的固定数据替换为相应的模板变量。

<div class="row info-line">
    <!--大标题-->
    <div class="col-lg-12 col-md-12 detail-header">
        <h3>{{ house[9] }}&nbsp{{ house[2] }}</h3>
        <div class="describe">
            <span>为您精准定位,当前城市房源信息</span>
        </div>
    </div>
    <!--左详情-->
    <div class="col-lg-8 col-md-8">
        <div class="course">

            <!--图-->
            <div><a href="#"><img class='img-fluid img-box' src="/static/img/house-bg1.jpg" alt=""></a>
            </div>
            <!--价格-->
            <div class="house-info">
                <span class="price">¥&nbsp{{ house[4] }}/月</span>
                <span class="collection" id="btn-collection"><a><i class="fa fa-heart"
                                                                   aria-hidden="true"> 收藏</i></a></span>
            </div>

            <!--基本信息标题-->
            <div class="attribute-header">
                <h4>基本信息</h4>
            </div>
            <!--属性1-->
            <div class="row attribute-info">
                <div class="col-lg-2 col-md-2">
                    <span class="attribute-text">基本属性</span>
                </div>
                <div class="col-lg-4 col-md-4">
                    <div>
                        <span class="attribute-text">房屋户型:</span>
                        <span class="info-text">{{ house[2] }}</span>
                    </div>
                    <div>
                        <span class="attribute-text">建筑面积:</span>
                        <span class="info-text">{{ house[3] }}平方米</span>
                    </div>
                    <div>
                        <span class="attribute-text">房屋朝向:</span>
                        <span class="info-text">{{ house[5] }}</span>
                    </div>
                </div>
                <div class="col-lg-6 col-md-6">
                    <div>
                        <span class="attribute-text">所在区域:</span>
                        <span class="info-text">{{ house[9] }}</span>
                    </div>
                    <div>
                        <span class="attribute-text">租住类型:</span>
                        <span class="info-text">{{ house[6] }}</span>
                    </div>
                    <div>
                        <span class="attribute-text">房东电话:</span>
                        <span class="info-text">{{ house[18] }}</span>
                    </div>
                </div>
            </div>
            <!--属性2-->
            <div class="row attribute-info">
                <div class="col-lg-2 col-md-2">
                    <span class="attribute-text">房屋卖点</span>
                </div>
                <div class="col-lg-8 col-md-8">
                    <div>
                        <span class="attribute-text">交通条件:</span>
                        <span class="info-text">{{ house[10] | dealNone }}</span>
                    </div>
                    <div>
                        <span class="attribute-text">优势条件:</span>
                        <span class="info-text">{{ house[1] }}</span>
                    </div>
                </div>

            </div>

重启服务器,通过浏览器访问http://127.0.0.1:5000/house/1网址后,可以看到也买你中会展示ID为1的房源基本信息

9.1.2 房源配套设施展示

房源配套设施呈现在详情页左侧基本信息的下方,用于给用户罗列租赁房屋标准配置的家用电器。

房源标准的配套设施有冰箱、洗衣机、电视、空调、暖气、热水器、天然气、床、WIFI和电梯,其中房屋没有配置的设施会使用带删除线的灰字显示。

1.后端实现

房源配套设施展示与房源基本信息展示的后端逻辑类似,首先通过SQLALchemy从数据库中查询字段为facilities的数据,并将获取的数据传递到模板文件后渲染到页面。由于facilities字段对应的数据格式为“设施1-设施2-设施3…-设施N”,即使用-连接了多个已有设施的名称,比如床-宽带-洗衣机-空调-热水器-暖气,所以这里需要将facilities字段对应的数据进行分割处理,以获得每个设施的名称。

在app.py文件中修改detail()函数,增加处理配套设施的代码。

# 实现房源数据展示
@app.route('/house/<int:hid>')
def detail(hid):
    sql="select * from house_info where id=%s"
    cursor.execute(sql,(hid,))
    # 从数据库查询房源ID为hid的房源对象
    house = cursor.fetchall()[0]
    # 获取房源对象的配套设施,比如床-宽带-洗衣机-空调-热水器-暖气
    facilities_str = house[12]
    # 将分隔后的每个设施名称保存到列表中
    facilities_list = facilities_str.split('-')

    return render_template('detail_page.html', house=house,facilities=facilities_list)

2.渲染模板

在detail_page.html文件中,查询class属性值为row attribute-info的div标签,在该标签内部通过选择结构判断配套设施是否存在,若存在则正常显示文字,否则显示为带删除线的文字。

<!--房源配套设施-->
<div class="attribute-header">
    <h4>房源配套设施</h4>
</div>
<!--设施列表-->
<div class="row attribute-info">
    <div class="col-lg-2 col-md-2">
        <span class="icon-1"></span>

        {% if '冰箱' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">冰箱</span>
        {% else %}
        <span class="attribute-text-sm"><s>冰箱</s></span>
        {% endif %}
    </div>
    <div class="col-lg-2 col-md-2">
        <span class="icon-2"></span>
        {% if '洗衣机' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">洗衣机</span>
        {% else %}
        <span class="attribute-text-sm"><s>洗衣机</s></span>
        {% endif %}

    </div>
    <div class="col-lg-2 col-md-2">
        <span class="icon-3"></span>

        {% if '电视' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">电视</span>
        {% else %}
        <span class="attribute-text-sm"><s>电视</s></span>
        {% endif %}
    </div>
    <div class="col-lg-2 col-md-2">
        <span class="icon-4"></span>

        {% if '空调' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">空调</span>
        {% else %}
        <span class="attribute-text-sm"><s>空调</s></span>
        {% endif %}
    </div>
    <div class="col-lg-2 col-md-2">
        <span class="icon-5"></span>

        {% if '暖气' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">暖气</span>
        {% else %}
        <span class="attribute-text-sm"><s>暖气</s></span>
        {% endif %}

    </div>
</div>
<div class="row attribute-info">
    <div class="col-lg-2 col-md-2">
        <span class="icon-6"></span>

        {% if '热水器' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">热水器</span>
        {% else %}
        <span class="attribute-text-sm"><s>热水器</s></span>
        {% endif %}
    </div>
    <div class="col-lg-2 col-md-2">
        <span class="icon-7"></span>

        {% if '天然气' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">天然气</span>
        {% else %}
        <span class="attribute-text-sm"><s>天然气</s></span>
        {% endif %}
    </div>
    <div class="col-lg-2 col-md-2">
        <span class="icon-8"></span>

        {% if '床' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">床</span>
        {% else %}
        <span class="attribute-text-sm"><s>床</s></span>
        {% endif %}
    </div>
    <div class="col-lg-2 col-md-2">
        <span class="icon-9"></span>

        {% if '宽带' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">Wi-Fi</span>
        {% else %}
        <span class="attribute-text-sm"><s>Wi-Fi</s></span>
        {% endif %}
    </div>
    <div class="col-lg-2 col-md-2">
        <span class="icon-10"></span>

        {% if '电梯' in facilities %}
        <span class="attribute-text-sm" style="color: #f1c40f">电梯</span>
        {% else %}
        <span class="attribute-text-sm"><s>电梯</s></span>
        {% endif %}
    </div>
</div>

重启服务器,在浏览器中刷新当前页面,通过改变房源的ID值,查看效果

9.2 利用ECharts实现数据可视化

9.2.1 认识数据可视化

数据可视化是指将大型数据集中的数据以图形图像形式表示,并利用数据分析和开发工具发现其中未知信息的处理过程。 数据可视化提倡美学形式与功能需要齐头并进,它既不会因为要实现功能用途而令人枯燥乏味,也不会因为要实现绚丽多彩的视觉效果而令图表过于复杂,而是直观地传达关键的方面与特征,从而实现对于相当稀疏而又复杂的数据集的深入洞察。

数据可视化的基本流程是从源数据到数据展示的完整过程:首先从源数据中选择与目标需求关系紧密的目标数据,然后对目标数据进行一系列处理操作,比如填充缺失值、删除重复值、替换异常值等,生成预处理数据,接着将预处理数据变换成变换数据,使变换数据的结构符合可视化工具的要求,最后借助数据可视化工具将变换数据渲染到网页上进行展示。

9.2.2 认识ECharts

ECharts是一款基于JavaScript语言的数据可视化库,它提供了直观生动、可交互、可个性化定制的图表,能够流畅地运行在PC或移动设备上。 ECharts支持多达39种图表,常用的图表有折线图、柱状图、散点图、饼图,用于地理数据可视化的图表有地图、热力图、线图,用于关系数据可视化的图表有关系图、树图、旭日图等。与此同时,ECharts还提供了标题、详情气泡、图例、值域、数据区域、时间轴、工具箱等7个可交互组件,支持多个图表、组件联动和混搭展现。

1.折线图

折线图一般由X轴(横轴)、Y轴(纵轴)、数据点和趋势线构成,用于描述一组或多组数据在有序数据类别(多为时间序列)上的变化情况,反映数据增减的规律、速率、峰值、谷值等特征。

数据点标注为圆点,并沿着日期或时间的先后顺序串联成趋势线。折线的峰值和谷值趋势线的走势可以直观地看出,数据随着随着时间变化的趋势。

2.柱状图

柱状图用于描述分类数据,并统计每个分类的数量,通过矩形条的高度反映各分类的数量差异。柱状图基本由X轴(横轴)、Y轴(纵轴)、纵向矩形条构成。

在基础柱状图中,每个纵向矩形条对应一个分类,其高度代表分类的数量;在多柱形图中,两个纵向矩形条对应一个分类,为了区分每组矩形条所代表的含义,图表上方增加了图例(图形或颜色所表示含义的说明,通常集中标注于图表的上方或一侧)加以说明。

3.散点图

散点图又称X-Y图,一般由X轴(横轴)、Y轴(纵轴)、数据点构成,用于比较两个类别之间是否存在某种关联,通过数据点的分布情况体现两个类别的相关性:若所有的数据点在一条直线附近呈波动趋势,说明两个类别是线性相关的;若数据点在曲线附近呈波动趋势,说明两个类别是非线性相关的;若数据点呈现其他情况,说明两个类别是不相关的。

4.饼图

饼图一般由若干个扇形构成,它使用圆代表数据的总量,组成圆的每个扇形表示每个分类占数据总量的比例大小,帮助用户快速了解数据中不同分类的分配情况。

组成圆形的每个扇形代表一个分类,每个扇形的面积代表该分类占总体的比例大小,所有扇形的比例相加的和等于100%。

9.2.3 ECharts的基本使用

ECharts支持绘制不同类型且样式丰富的图表,尽管每种图表的样式千差万别,但通过ECharts绘制这些图表的流程基本相同。使用ECharts绘制图表的流程一般分为以下4步。 (1)在HTML文件中引入ECharts。 (2)定义有宽度和高度的父容器。 (3)初始化一个 ECharts实例。 (4)通过setOption()方法生成图表。

1.在HTML文件中引入ECharts

使用ECharts绘制图表之前,需要先在HTML文件中引入包含完整ECharts功能的脚本文件echarts.min.js。例如,创建一个名称为test的HTML文件,在该文件中的head标签内部引入ECharts.js文件。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <!-- 引入ECharts -->
        <script src="echarts.min.js"></script>
    </head>
    <body> 
    </body>
</html>

2.定义有宽度和高度的父容器

为了让图表具有预定的宽度和高度,我们可以在HTML文件中使用 div标签定义一个父容器,并且通过CSS代码指定该父容器具有宽度和高度。当创建图表的时候,图表的宽度和高度默认为父容器的宽度和高度,无须另行指定。

<body> 
    <!—定义具备宽度和高度的父容器 -->
    <div id="main" style="width: 600px;height:400px;"></div>
 </body>

3.初始化一个ECharts实例

echarts.init()函数用于初始化一个ECharts实例。ECharts是ECharts提供的全局对象,该对象会在script中引入 echarts.min.js文件后获得。

echarts.init(dom: HTMLDivElement|HTMLCanvasElement, 
    theme: Object|string,
    opts: { devicePixelRatio: number, renderer: string,
    useDirtyRect: boolean, width: number|string, height: number|string,
    locale: string }) => ECharts

ECharts.init()函数常用参数如下。

(1)dom:表示实例容器,一般是一个具有宽度和高度的< div>元素。

(2)theme:表示应用的主题。

(3)opts:附加参数,支持以下可选项。

devicePixelRatio:设备像素比,默认值为通过window.devicePixelRatio获取浏览器的值。 renderer:表示渲染器,支持'canvas'(Canvas渲染)或者'svg'(SVG渲染)两种取值。 useDirtyRect:是否开启矩形渲染,默认值为false。 width,height:可显式指定ECharts实例的宽度和高度,单位为像素。如果传入的值为null/undefined/'auto',则表示自动获取dom的宽度。 locale:使用的语言,内置 'ZH' 和 'EN' 两种语言。

在body标签中增加初始化ECharts实例的代码。

<body> 
    <!—定义具备宽度和高度的父容器 -->
    <div id="main" style="width: 600px;height:400px;"></div>
    <script type="text/javascript">
        // 初始化ECharts实例
        var myChart = echarts.init(document.getElementById('main'));
    </script>
</body>

4.通过setOption()函数生成图表

setOption()函数可以为图表指定配置项和数据,以生成一个指定样式的图表。

setOption(option: Object, notMerge: boolean, lazyUpdate: boolean) 

option:图表的配置项以及数据。例如,配置项series用于设置图表的类型和存放图表的数据。 notMerge:是否不与之前设置的option进行合并,默认值为 false,表示合并。 lazyUpdate:设置完option后是否不立即更新图表,默认值为 false,代表立即更新。

在script与/script标签之间增加生成图表的代码。

<script type="text/javascript">
    // 初始化ECharts实例
    var myChart = echarts.init(document.getElementById('main'));
    //指定配置项
    var option={
        title:{ //设置标题
          text:'ECharts入门示例'
        },
        tooltip:{},//设置提示框
        legend:{
            data:['销量'],
        },
        xAxis:{ //设置x轴的类目
            data:["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
        },
        yAxis:{ },//设置y轴的类目
        series:[{//设置图表类型和数据
            name:'销量',
            type:'bar',
            data:[5,20,36,10,10,20]
        }]
    };
    //使用刚指定的配置项生成图表
    myChart.setOption(option);
</script>

在app.py文件中定义一个函数用于访问该页面

# 测试eCharts图表
@app.route("/test/eCharts")
def test():
    return render_template("testECharts.html")

通过浏览器访问“http://127.0.0.1:5000/test/eCharts”地址,查看效果

9.2.4 ECharts的常用配置项

为了使图表传达准确的信息,ECharts内置了众多配置项,用于为图表设置辅助元素、组件动画等。ECharts中常用的配置项包括title、legend、xAxis和yAxis、grid、tooltip、series。

1.title

title配置项用于为图表设置主标题和副标题,帮助用户快速理解图表的意图。

属性说明
show是否显示标题,默认值为True
link设置主标题的文本超链接
target设置哪个窗口打开主标题超链接。target属性支持'self'和'blank'两种取值,其中'self'表示当前窗口打开,'blank'表示新窗口打开
fontSize设置主标题文字的字体大小,默认值为18像素
subtext设置副标题文本,默认为空字符串
textAlign设置主标题和副标题整体的水平对齐方式,默认值为'auto'

2.legend

legend配置项用于为图表设置图例,便于用户了解图表中不同标记、颜色等对应系列。可以通过单击图例项来控制哪些系列显示或隐藏。当系列数量过多时,可以使用能滚动翻页的图例。

属性说明
style设置图例的类型,支持'plain'(普通图例)和'scroll'(可滚动翻页的图例)两种取值,默认值为'plain'
show是否显示图例,默认值为true
width设置图例的宽度,默认值为'auto',表示自适应
height设置图例的高度,默认值为'auto',表示自适应
backgroundColor设置图例的背景颜色,默认值为'transparent',代表透明
borderColor设置图例的边框颜色,默认值为'#ccc'
borderWidth设置图例的边框线宽,默认值为1像素
animation设置图例翻页是否使用动画

3.xAxis和yAxis

xAxis和yAxis配置项用于设置图表(必须有直角坐标系)的X轴和Y轴。

属性说明
show是否显示X轴或Y轴,默认值为true
position是设置X轴或Y轴的位置,默认第一个X轴位于下方,第二个X轴位于第一个X轴的另一侧;第一个Y轴位于左方,第二个Y轴位于第一个Y轴的另一侧
type设置坐标轴的类型,默认值为'category',表示类目轴,适用于离散的类目数据。该参数还支持'value'(数值轴,适用于连续数据)、'time'(时间轴,适用于连续的时序数据)和'log'(对数轴,适用于对数数据)三种取值
name设置坐标轴的名称
nameLocation设置坐标轴名称的显示位置,默认值为'end',代表在轴结束端显示
属性说明
axisTick坐标轴刻度相关设置
data类目数据,在类目轴上生效
axisLabel坐标轴刻度标签的相关设置
axisLine坐标轴轴线的相关设置
boundaryGap坐标轴两边留白策略

4.grid

grid配置项用于设置图表(必须有直角坐标系)的网格。

属性说明
containLabel设置网络区域内是否包含坐标轴的刻度标签
left网格区域离容器左侧的距离,默认值为'10%'。该参数支持3种类型的取值,第1种是具体的像素值,比如10;第2种是相对于容器高宽的百分比,比如'10%';第3种是表示位置的字符串,可以为'left'、'center'、'right'
right网格区域离容器右侧的距离,默认值为'10%'
top网格区域离容器顶部的距离,默认值为60。该参数支持3种类型的取值,第1种是具体的像素值,比如10;第2种是相对于容器高宽的百分比,比如'10%';第3种是表示位置的字符串,可以为'top'、'middle'、'bottom'
bottom网格区域离容器底部的距离,默认值为60

5.tooltip

tooltip配置项用于为图表设置提示框组件,提示框组件用于显示鼠标悬浮在图形上方的提示内容。

属性说明
trigger触发类型,支持3种取值,其中'item'表示数据项图形触发,主要在散点图、饼图等无类目轴的图表中使用;'axis'表示坐标轴触发,主要在柱状图、折线图等使用类目轴的图表中使用;'none'表示不触发
formatter设置提示框浮层的内容格式器,支持字符串模板和回调函数两种形式
textStyle设置提示框浮层的文本样式

6.series

series是一个非常重要的配置项,主要用于设置图表的类型以及存放图表的数据,适用于所有类型的图表。

属性说明
name设置系列的名称,用于提示框的显示和图例筛选
type设置图表的类型。例如,bar表示柱状图/条形图,line表示折线图,pie表示饼图,scatter表示散点图或气泡图
radius设置饼图的半径,支持3种类型的取值,分别是number、string和Array.<number|string>,其中number代表指定的外半径值;string代表外半径为可视区尺寸(容器高宽中较小一项)的长度;Array.<number|string>表示数组,数组的第一项是内半径,第二项是外半径
center设置饼图的中心(圆心)坐标,坐标值为包含两个元素的数组,数组的第一项是横坐标(数值)或相对于容器宽度(百分比字符串),第二项是纵坐标(数值)或相对于容器高度(百分比字符串)
labelLine标签的视觉引导线配置
label饼图图形上的文本标签,可用于说明图形的一些数据信息,比如值,名称等
itemStyle设置图形样式

9.3 户型占比可视化

9.3.1 户型占比可视化的功能分析

户型占比可视化功能用于统计当前房源所属街道范围内各种户型房源的数量,并通过饼图展示各种户型的占比情况,方便用户了解当前街道范围内的房源哪种户型偏多,所需户型的房源是否容易找到。

每个扇形代表着不同的户型,每个扇形大小代表不同户型的房源数量占房源总数量的比例。

户型占比可视化功能的实现流程遵循数据可视化的基本流程。

户型占比可视化功能各环节的说明如下。

(1)源数据:数据库的house_info表中存放着所有房源的数据。 (2)目标数据:从数据库的house_info表中筛选出与当前房源所属同一街道的全部房源数据。由于house_info表中block字段对应的一列数据为房源所属街道,所以需要从数据库中查询与当前房源对象的block字段值相同的数据。 (3)预处理数据:将目标数据按照户型进行分类,并统计每种户型的房源数量。 (4)变换数据:将户型分类和户型数量按照ECharts要求变换成指定格式的数据。 (5)数据展示:通过数据可视化工具ECharts将户型分类和户型数量绘制成饼图。

9.3.2 户型占比可视化的接口设计

为了确保详情页可以正确显示饼图,需要提前明确户型占比可视化的接口信息。

接口描述说明
请求页面detail_page.html
请求方式GET
请求地址/get/piedata/<block>
返回数据JSON格式的数据,包括户型和数量

9.3.3 获取同街道房源的户型和数量

获取同街道房源的户型和数量的逻辑为:前端首先向后端发送一个Ajax请求告知获取哪条街道的房源数据,然后后端到数据库中查询并过滤属于该街道的所有房源数据,将这些房源数据按户型进行分组后统计每种户型的房源数量,最后将户型分类和户型数量组装成指定格式的JSON数据后传给ECharts生成图表即可。

获取同街道房源的户型和数量,具体步骤如下。 (1)在detail_page.html文件中,编写发送Ajax请求的代码。

$.ajax({
    url: "/get/piedata/{{ house[8] }}",
    type: 'get',
    dataType: 'json',
    success: function (data) {
        pie_chart(data['data'])
    }
});

(2)按照定义的户型占比可视化的接口,定义视图函数return_pie_data()。

# 实现户型占比功能
@app.route('/get/piedata/<block>')
def return_pie_data(block):
    # 从房源信息表中按房子大小进行分组,并根据每组规格的房子数量进行降序排序后,查询出所有房源规格以及各个房源的总套数
    sql="SELECT rooms,COUNT(*) FROM house_info WHERE block= %s GROUP BY rooms ORDER BY COUNT(*) DESC"
    time.sleep(3)
    cursor.execute(sql, (block))
    result = cursor.fetchall()
    data = []
    for one_house in result:
        data.append({'name': one_house[0], 'value': one_house[1]})
    return jsonify({'data': data})

9.3.4 通过饼图展示户型占比

当浏览器发送的Ajax请求成功后,会调用pie_chart()函数通过ECharts绘制饼图。在house项目的static/js目录下,show_data_pie.js文件中存放了pie_chart()函数的代码。

function pie_chart(data) {
    // 初始化一个 Echarts实例
    var myChart = echarts.init(document.getElementById('pie'));

    window.addEventListener('resize', function () {
        myChart.resize();
    });

    var option = {
        tooltip : {
            trigger: 'item',
            formatter: "{a} <br/>{b} : {c} ({d}%)",
        },

        series:[{

            name: '户型的占比',

            type: 'pie',

            radius: ['0%', '50%'],
            center: ['50%', '60%'],

            labelLine: {

                normal: {
                    show: true
                },
                // 选中后加重表现
                emphasis: {
                    show: true
                }
            },
            // 饼状图的内部名字
            label: {
                normal: {
                    show: true
                },
                emphasis: {
                    show: true
                }
            },
            //
            itemStyle: {
                emphasis: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                }
            },
            data: data,
        }]
    };

    myChart.setOption(option); // 根据配置项生成图表
}

对detail_page.html文件中设置饼图标题的HTML代码进行修改,使该标题中的街道名称跟随房源进行动态变化。

<!--pie-->
<div class="col-lg-12 col-md-12 mx-auto attribute-header">
    <h4><i class="fa fa-align-right" aria-hidden="true"></i>&nbsp&nbsp{{ house[8] }}
        户型占比</h4>
    <div class="attribute-header-tip-line">
        <span>根据户型占比,了解户型稀缺度</span>
    </div>
</div>

重启服务器,根据房源信息的ID值查询,刷新当前页面

9.4 小区房源数量TOP20可视化

9.4.1 小区房源数量TOP20可视化的功能分析

小区房源数量TOP20可视化功能用于统计与当前房源所在街道上各小区在租房源的数量,并通过柱状图展示在租房源数量位于前20名的小区,方便用户了解当前街道范围内哪个小区的房源偏多。 每个矩形条代表一个小区,每个矩形条的高度代表小区的房源数量。

小区房源数量TOP20可视化功能的实现流程遵循数据可视化的基本流程。

小区房源数量TOP20可视化各环节的说明如下。 (1)源数据:数据库的house_info表中存放着所有房源的数据。 (2)目标数据:从数据库的house_info表中筛选出与当前房源所属同一街道的全部房源数据。 (3)预处理数据:将房源数据按照小区名称进行分类,并统计各小区房源的总数量。 (4)变换数据:将小区名称和房源数量按照ECharts要求变换成指定格式的数据。 (5)数据展示:通过数据可视化工具ECharts将小区名称和房源数据绘制成柱状图。

9.4.2 小区房源数量TOP20可视化的接口设计

为了确保详情页可以正确显示柱状图,需要提前明确小区房源数量TOP20可视化的接口信息。 小区房源数量TOP20可视化的接口如表所示。

接口描述说明
请求页面detail_page.html
请求方式GET
请求地址/get/columndata/<block>
返回数据JSON格式的数据

9.4.3 获取小区房源数量TOP20

获取小区房源数量TOP20的逻辑为:前端首先向后端发送一个Ajax请求告知获取哪条街道的房源数据,然后后端到数据库中查询并过滤同属该街道的所有房源数据,将这些房源数据按小区名称进行分组后统计各小区的房源数量,最后将位于前20名的小区名称和房源数量组装成指定格式的JSON数据后传给ECharts生成图表即可。

获取小区房源数量TOP20,具体如下。 (1)在detail_page.html文件中,编写了发送Ajax请求的代码。

// 获取column
$.ajax({
    url: "/get/columndata/{{ house[8] }}",
    type: 'get',
    dataType: 'json',
    success: function (data) {
        column_chart(data['data'])
    }
});

 (2)按照接口定义一个视图函数return_bar_data(),该函数用于与当前房源同街道的房源数据经过预处理、变换后,返回数据可视化工具ECharts要求JSON格式的数据。

# 实现本地区小区数量TOP20功能
@app.route('/get/columndata/<block>')
def return_bar_data(block):
    sql = "SELECT address,COUNT(*) FROM house_info WHERE block= %s GROUP BY address ORDER BY COUNT(*) DESC"
    # 数据量庞大,ajax属于异步访问,这里休眠5秒,等上一个数据库查询结束后再执行
    time.sleep(5)
    cursor.execute(sql, (block))
    result = cursor.fetchall()

    name_list = []
    num_list = []
    for addr, num in result:
        residence_name = addr.rsplit('-', 1)[1]
        name_list.append(residence_name)
        num_list.append(num)
    if len(name_list) > 20:
        data = {'name_list_x': name_list[:20], 'num_list_y': num_list[:20]}
    else:
        data = {'name_list_x': name_list, 'num_list_y': num_list}
    return jsonify({'data': data})

9.4.4 通过柱状图展示小区房源数量TOP20

当浏览器发送的Ajax请求成功后,会调用column_chart()函数通过ECharts绘制柱状图。在house项目的static/js目录下,show_column_data.js文件中存放了column_chart()函数的代码。

function column_chart(data) {

    var salaru_line = echarts.init(document.getElementById('scolumn_line'));
    window.addEventListener('resize', function () {
        salaru_line.resize();
    });
    var XData = data['name_list_x']; // X轴的数据
    var YData = data['num_list_y'];  // Y轴的数据

    var dataMin = parseInt(Math.min.apply(null, YData) / 2);

    var option = {
        backgroundColor: "#fff",
        grid: {
            height: '200px',
            width: '320px',
            left: '50px'
        },
        xAxis: {
            axisTick: {
                show: false
            },
            splitLine: {
                show: false
            },
            splitArea: {
                show: false
            },
            data: XData,
            axisLabel: {
                formatter: function (value) {
                    var ret = ""; // 拼接类目项
                    var maxLength = 1; // 每项显示文字个数
                    var valLength = value.length; // X轴类目项的文字个数
                    var rowN = Math.ceil(valLength / maxLength); // 类目项需要换行的行数
                    if (rowN > 1) // 如果类目项的文字个数大于3,
                    {
                        for (var i = 0; i < rowN; i++) {
                            var temp = ""; // 存放每次截取的字符串
                            var start = i * maxLength; // 开始截取的位置
                            var end = start + maxLength; // 结束截取的位置
                            temp = value.substring(start, end) + "\n";
                            ret += temp; // 拼接最终得到的字符串
                        }
                        return ret;
                    } else {
                        return value;
                    }
                },
                interval: 0,
                fontSize: 11,
                fontWeight: 100,
                textStyle: {
                    color: '#555',

                }
            },
            axisLine: {
                show: {
                    color: '#4d4d4d'
                }
            }
        },
        yAxis: {
            name: '房源数量/套',
            nameLocation: 'center',
            nameGap: 35,

            axisTick: {
                show: false
            },
            splitLine: {
                show: false
            },
            splitArea: {
                show: false
            },
            min: dataMin,
            axisLabel: {
                textStyle: {
                    color: '#9faeb5',
                    fontSize: 12,
                }
            },
            axisLine: {
                lineStyle: {
                    color: '#4d4d4d'
                }
            }
        },
        "tooltip": {
            "trigger": "item",
            "textStyle": {
                "fontSize": 12
            },
            "formatter": "{b0}: {c0}套"
        },
        series: [{
            type: "bar",
            itemStyle: {
                normal: {
                    color: {
                        type: 'linear',
                        x: 0,
                        y: 0,
                        x2: 0,
                        y2: 1,
                        colorStops: [{
                            offset: 0,
                            color: '#00d386' // 0% 处的颜色
                        }, {
                            offset: 1,
                            color: '#0076fc' // 100% 处的颜色
                        }],
                        globalCoord: false // 缺省为 false
                    },
                    barBorderRadius: 15,
                }
            },
            data: YData,
            label: {
                show: true,
                position: 'top',
                "fontSize": 10
            }
        }]
    };
    salaru_line.setOption(option, true);
}

 设置完column_chart()函数后,还需要对detail_page.html文件中设置柱状图标题的HTML代码进行修改,使该标题中的街道名称跟随房源数据进行同步修改。

<!--pie2-->
<div class="col-lg-12 col-md-12 mx-auto attribute-header">
    <h4><i class="fa fa-align-right" aria-hidden="true"></i>&nbsp&nbsp{{ house[8] }}
        小区房源数量TOP20</h4>
    <div class="attribute-header-tip-line">
        <span>关注房源数量,了解房源热点</span>
    </div>
</div>
<div class="col-lg-12 col-md-12 mx-auto browse-record-first-div self-no-padding">
    <div id="scolumn_line">
    </div>
</div>

重启服务器,刷新当前页面,查看效果

9.5 户型价格走势可视化

9.5.1 户型价格走势可视化的功能分析

户型价格走势可视化功能用于统计当前房源所属街道1室1厅、2室1厅 、2室2厅、3室2厅这4种户型房源的平均价格(价格/面积),并通过折线图展示近14天(从最新发布时间开始往前推14天)这4种户型房源的价格走势,方便用户了解当前街道这4种户型房源的市场行情。

 

户型价格走势可视化功能的实现流程遵循数据可视化的基本流程,如图所示。

 

户型价格走势可视化功能各环节的说明如下。 (1)源数据:数据库的house_info表中存放着所有房源的数据。 (2)目标数据:从数据库的house_info表中筛选出与当前房源所属同一街道的全部房源数据。 (3)预处理数据:将1室1厅、2室1厅 、2室2厅、3室2厅这4种户型的房源数据按照发布时间进行分类,并计算这4种户型房源近14天的平均价格,并准备由发布时间构成的时间序列。 (4)变换数据:将4种户型的平均价格和时间序列按照ECharts要求变换成指定格式的数据。 (5)数据展示:通过数据可视化工具ECharts将平均价格和时间序列绘制成折线图。

9.5.2 户型价格走势可视化的接口设计

为了确保详情页可以正确显示折线图,需要提前明确户型价格走势可视化的接口信息。

接口描述说明
请求页面detail_page.html
请求方式GET
请求地址/get/columndata/<block>
返回数据JSON格式的数据

9.5.3 获取平均价格和时间序列

获取平均价格和时间序列的逻辑具体如下: (1)获取平均价格。前端首先向后端发送一个Ajax请求告知获取哪条街道的房源数据,后端需要到数据库中查询并过滤该街道上1室1厅、2室1厅 、2室2厅、3室2厅这4种户型的房源数据,然后将这些数据按照发布时间进行排序后计算平均价格,最后取出近14天的平均价格。 (2)获取时间序列。前端首先向后端发送一个Ajax请求告知获取哪条街道的房源数据,后端然后到数据库中查询并过滤当前房源所属街道的房源发布时间,由于发布时间是秒数,所以这里将发布时间的格式转换为以“x月x日”,并添加到列表中。

获取平均价格和时间序列,具体步骤如下。 (1)在detail_page.html文件中,编写了发送Ajax请求的代码。

// 获取broken_line
$.ajax({
    url: "/get/brokenlinedata/{{ house[8] }}",
    type: 'get',
    dataType: 'json',
    success: function (data) {
        broken_line_chart(data['data'])
    }
});

(2)按照接口定义一个视图函数return_brokenline_data(),该函数用于将近14天同一街道1室1厅、2室1厅 、2室2厅、3室2厅这4种户型房源的平均价格和时间序列经过预处理、变换后,返回数据可视化工具ECharts要求的JSON格式的数据。

# 实现户型价格走势
@app.route('/get/brokenlinedata/<block>')
def return_brokenline_data(block):
    # 时间序列
    sql="select publish_time from house_info where block = %s"
    time.sleep(7)
    cursor.execute(sql,(block))
    time_stamp = cursor.fetchall()
    list(time_stamp).sort(reverse=True)
    date_li = []
    # date_li.append(datetime.fromtimestamp(int(time_stamp[0][0])).strftime("%m-%d"))
    for i in range(1, 14):
        latest_release = datetime.fromtimestamp(int(time_stamp[0][0]))
        day = latest_release + timedelta(days=-i)
        date_li.append(day.strftime("%m-%d"))
    date_li.reverse()

    # 1室1厅的户型
    sql="select avg(price/area) from house_info where block=%s and rooms=%s group by publish_time order by publish_time"
    cursor.execute(sql,(block,"1室1厅"))
    result = cursor.fetchall()
    data = []
    for i in result[-14:]:
        data.append(round(i[0], 2))
    # 2室1厅的户型
    cursor.execute(sql, (block, "2室1厅"))
    result1 = cursor.fetchall()
    data1 = []
    for i in result1[-14:]:
        data1.append(round(i[0], 2))

    # 2室2厅的户型
    cursor.execute(sql, (block, "2室2厅"))
    result2 = cursor.fetchall()
    data2 = []
    for i in result2[-14:]:
        data2.append(round(i[0], 2))
    # 3室2厅的户型
    cursor.execute(sql, (block, "3室2厅"))
    result3 = cursor.fetchall()
    data3 = []
    for i in result3[-14:]:
        data3.append(round(i[0], 2))
    return jsonify({'data': {'1室1厅': data, '2室1厅': data1, '2室2厅': data2, '3室2厅': data3, 'date_li': date_li}})

9.5.4 通过折线图展示户型价格走势

当浏览器发送的Ajax请求成功后,会调用broken_line_chart()函数通过ECharts绘制折线图。在house项目的static/js目录下,show_broken_line_data.js文件中存放了broken_line_chart()函数的代码。

function broken_line_chart(data) {

    var salaru_line = echarts.init(document.getElementById('broken_line'), 'infographic');
    window.addEventListener('resize', function () {
        salaru_line.resize();
    });

    var Data1 = data['3室2厅'];
    var Data2 = data['2室2厅'];
    var Data3 = data['2室1厅'];
    var Data4 = data['1室1厅'];

    echartsDate = [];
    for (var i = 0; i < data['date_li'].length; i++) {
        d = data['date_li'][i]
        echartsDate.push(d);
    }

    var option = {
        tooltip: {
            trigger: 'axis',
        },
        legend: {
            data: ['3室2厅', '2室2厅', '2室1厅', '1室1厅']
        },
        grid: {
            containLabel: true,
            left: '5%',
            right: '4%',
            bottom: '3%'
        },

        xAxis: {
            type: 'category',
            boundaryGap: false,
            data: echartsDate
        },
        yAxis: {
            type: 'value',
            name: '平均价格/元',
            nameLocation: 'center',
            nameGap: 30,
            axisLine:{show:true}
        },
        series: [

            {
                name: '3室2厅',
                type: 'line',
                data: Data1
            },
            {
                name: '2室2厅',
                type: 'line',
                data: Data2
            },
            {
                name: '2室1厅',
                type: 'line',
                data: Data3
            },
            {
                name: '1室1厅',
                type: 'line',
                data: Data4
            }
        ]
    };

    salaru_line.setOption(option, true);
}
<!--line2-->
<div class="col-lg-12 col-md-12 mx-auto attribute-header">
    <h4><i class="fa fa-align-right" aria-hidden="true"></i>&nbsp&nbsp{{ house[8] }}
        户型价格走势</h4>
    <div class="attribute-header-tip-line">
        <span>关注房源单价,了解各小区房价</span>
    </div>
</div>

重启服务器,访问当前页面,查看效果

9.6 预测房价走势可视化

9.6.1 线性回归算法

线性回归是一种应用极为广泛的统计分析方法,该方法利用数理统计中的回归分析来确定两种或两种以上变量间相互依赖的定量关系。在回归分析中,若只有一个自变量和一个因变量,且二者的关系可使用一条直线近似表示,则称为一元线性回归分析;若有两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析。

y = w'x+e

y表示因变量,x表示自变量,e为误差服从均值为0的正态分布。

假设某公司去年各月产品销售额以及投入的广告费具体如表所示。

月份广告费(万元)销售额(万元)
1月49
2月820
3月922
4月815
5月717
6月1223
7月618
8月1025
9月610
10月920
11月1020
12月617

利用线性回归算法制作一条拟合直线,使这条直线尽可能符合广告费和销售额数据的分布情况。

 

每个圆点在直角坐标系中的位置是由广告费(单位:万元)和销售额(单位:万元)的值决定的,它的X值和Y值分别为广告费和销售额;直线是利用一元线性回归算法绘制的,该线条以广告费为自变量,销售额为因变量,描述了广告费和销售额之间的变化规律,即每个月投入的广告费越多,则销售额越高。

对于一元线性回归来说,可以看作Y值是随X值变化的,每个X值都会对应一个实际的Y值,此时的Y值称为Y实际;每个X值在直线上方都会有一个预测的Y值,此时的Y值称为Y预测。

 (Y1实际-Y1预测)^2 + (Y2实际-Y2预测)^2 +……+(Yn实际-Yn预测)^2

 

在上述公式中,“(Y1实际-Y1预测)^2”计算第一个圆点距直线的垂直距离,(Y2实际-Y2预测)^2计算第二个圆点距直线的垂直距离,以此类推,在计算出最后一个圆点距直线的垂直距离后,求所有距离的和,此时得到的斜率为最小的斜率。有了斜率,便可以绘制一条直线。 当我们输入一个新的X值时,程序会根据绘制的直线返回预测的Y值,也就是该X值所在的延长线与直线交叉的点的Y值。

9.6.2 认识scikit-learn库

scikit-learn是一个专门针对机器学习应用而开发的开源库,该库由社区成员自发维护,并不断地拓展机器学习领域涵盖的功能。scikit-learn依赖于NumPy、pandas、SciPy,它不仅支持分类、回归、降维和聚类等算法,还提供了特征提取、数据处理、模型评估共3大模块,在机器学习领域颇受欢迎。

在使用scikit-learn进行开发之前,需要先确保当前的虚拟环境中已经安装了scikit-learn库以及依赖项,包括NumPy、pandas、SciPy和sklearn,关于它们的安装命令如下所示。以下这些库中可能在下载其中某个过程中会把其他库一起下载。

pip install scikit-learn
pip install numpy
pip install pandas
pip install scipy
pip install sklearn

 

sklearn库内置了众多子模块,linear_model表示线性模型子模块,该模块中封装了多个线性模型。通过sklearn.linear_model模块实现线性回归功能一般需要以下4步。

(1)导入线性回归模型。

(2)创建线性回归模型。

(3)训练线性回归模型。

(4)预测新样本。

1.导入线性回归模型

sklearn.linear_model模块中提供了一个LinearRegression类,该类用于构造一个线性回归模型,它封装了线性回归模型的相关功能。因此,我们需要在程序中从linear_model模块导入LinearRegression类。

from sklearn.linear_model import LinearRegression

2.创建线性回归模型

如果希望得到一个线性回归模型,需要通过LinearRegression类的构造方法实例化LinearRegression类的对象。

LinearRegression(*, fit_intercept=True, normalize='deprecated', copy_X=True,n_jobs=None, positive=False)

fit_intercept:表示是否计算此模型的截距(分为横截距和纵截距,横截距是直线与X轴交点的横坐标,纵截距是直线与Y轴交点的纵坐标),默认值为True。 normalize:表示是否将数据进行归一化(一种简化计算的方式,即将有量纲的表达式变换为无量纲的表达式,成为标量),默认值为False。 copy_X:表示是否复制X,默认值为True。若设为False,则X会被覆盖。 n_jobs:表示使用CPU处理器的数量,默认值为1。 positive:表示强制系数是否为正,默认值为False。

3.训练线性回归模型

为了能够得到准确的预测值,需要根据数据训练线性回归模型。LinearRegression类提供了一个用于训练模型的fit()方法,fit()方法的语法格式如下所示。

fit(X, y, sample_weight=None)

X:表示训练的数据,该参数的值可以为数组或稀疏矩阵(在矩阵中,若数值为0的元素数目远远多于非0的元素数目,并且非0元素分布没有规律时,则称该矩阵为稀疏矩阵)。 y:表示目标值。 sample_weight:表示每个样本的单独权重。

4.预测新样本

LinearRegression类提供了用于预测新样本的predict()方法,predict()方法的语法格式如下所示。

predict(X)

X:表示新样本,该参数的值可以为数组或稀疏矩阵。

以广告费和销售额的数据为例,演示如何使用scikit-learn库实现预测销售额的功能。

在项目中创建TestSklearn.py文件,演示实现预测销售额的功能

# 导入线性回归模型
from sklearn.linear_model import LinearRegression
import numpy as np
# 基于线性回归模型实现预测功能的函数
def linear_model_main(X_parameter,Y_parameter,predict_value):
    # 创建线性回归模型
    regr=LinearRegression()
    # 训练线性回归模型
    regr.fit(X_parameter,Y_parameter)
    # 预测新样本
    predict_value=np.array([predict_value]).reshape(-1,1)
    predict_outcome=regr.predict(predict_value)
    # 返回预测的新值
    return predict_outcome
if __name__=="__main__":
    # 广告费和销售额
    x_data=[[4],[8],[9],[8],[7],[12],[6],[10],[6],[9],[10],[6]]
    y_data=[9,20,22,15,17,23,18,25,10,20,20,17]
    predict_value=6 #新样本值
    predict_outcome=linear_model_main(x_data,y_data,predict_value)
    print("预测结果:",predict_outcome)

预测结果: [14.5447648]

9.6.3 后端逻辑的分析与实现

预测房价走势可视化功能用于统计最近一个月当前房源所属街道范围内所有房源的平均价格,并通过散点图中的圆点展示房源平均价格与日期的关系,并使用回归线展示平均价格的走势,方便用户了解当前街道房源的市场行情。

图中所有圆点描述了近一个月房源平均价格与日期的关系,回归线描述了房源平均价格的走势。

后端主要负责计算近一个月当前房源所属街道范围内全部房源的平均价格,以及准备由发布时间构成的时间序列,之后将平均价格和时间序列传递给前端,由前端绘制成散点图。

1.接口设计

为了确保详情页可以正确显示散点图,需要提前明确预测房价走势可视化的接口信息。

接口描述说明
请求页面detail_page.html
请求方式GET
请求地址/get/columndata/<block>
返回数据JSON格式的数据

2.代码实现

后端的实现逻辑主要分为以下两种情况:

(1)获取平均价格。前端首先向后端发送一个Ajax请求告知获取哪条街道的房源数据,后端需要到数据库中查询并过滤该街道的全部房源数据,然后将这些房源数据按照发布时间进行分组,计算每个分组的平均价格,最后取出近1个月的平均价格。

(2)获取时间序列。前端首先向后端发送一个Ajax请求告知获取哪条街道的房源数据,后端需要到数据库中查询并过滤该街道全部房源的发布时间,由于发布时间是秒数,所以这里将发布时间的格式由秒数转换为“x月x日”,并添加到列表中。

有了平均价格和时间序列后,便可以将平均价格和时间序列组装成指定格式的JSON数据后传给ECharts生成图表。分步骤介绍如何获取平均价格和时间序列。

(1)在detail_page.html文件中,编写了发送Ajax请求的代码。

// 获取scatter
$.ajax({
    url: "/get/scatterdata/{{ house[8] }}",
    type: 'get',
    dataType: 'json',
    success: function (data) {
        getdata1(data['data']);

    }
});

(2)按照定义的接口定义一个视图函数return_scatter_data(),该函数用于将近1个月同一街道全部房源数据的平均价格和时间序列经过预处理、变换后,返回数据可视化工具ECharts要求的JSON格式的数据。

# 实现房价预测功能
@app.route('/get/scatterdata/<block>')
def return_scatter_data(block):
    # 获取时间序列
    sql="select avg(price/area) from house_info where block=%s group by publish_time order by publish_time"
    time.sleep(1)
    cursor.execute(sql,(block))
    result = cursor.fetchall()
    sql="select publish_time from house_info where block=%s"
    cursor.execute(sql,(block))
    time_stamp = cursor.fetchall()
    # 将元组转换成列表,再进行倒序
    list(time_stamp).sort(reverse=True)
    date_li = []
    for i in range(1, 30):
        latest_release = datetime.fromtimestamp(int(time_stamp[0][0]))
        day = latest_release + timedelta(days=-i)
        date_li.append(day.strftime("%m-%d"))
    date_li.reverse()
    # 获取平均价格
    data = []
    x = []
    y = []
    for index, i in enumerate(result):
        x.append([index])
        y.append(round(i[0], 2))
        data.append([index, round(i[0], 2)])
    # 对未来一天的价格进行预测
    predict_value = len(data)
    predict_outcome = linear_model_main(x, y, predict_value)
    p_outcome = round(predict_outcome[0], 2)
    # 将预测的数据添加入data中
    data.append([predict_value, p_outcome])
    return jsonify({'data': {'data-predict': data, 'date_li': date_li}})

# 导入线性回归模型
from sklearn.linear_model import LinearRegression
import numpy as np
# 基于线性回归模型实现预测功能的函数
def linear_model_main(X_parameter,Y_parameter,predict_value):
    # 创建线性回归模型
    regr=LinearRegression()
    # 训练线性回归模型
    regr.fit(X_parameter,Y_parameter)
    # 预测新样本
    predict_value=np.array([predict_value]).reshape(-1,1)
    predict_outcome=regr.predict(predict_value)
    # 返回预测的新值
    return predict_outcome

9.6.4 通过散点图展示预测房价走势

当浏览器发送的Ajax请求成功后,会调用getdata1()函数通过ECharts绘制散点图。在house项目的static/js目录下,f_data.js文件中存放了getdata1()函数的代码。

function getdata1(data) {
    var center1 = echarts.init(document.getElementById('f_line'), 'infographic');
    window.addEventListener('resize', function () {
        center1.resize();
    });

    var myRegression = ecStat.regression('linear', data['data-predict']);
    myRegression.points.sort(function (a, b) {
        return a[0] - b[0];
    });

    echartsDate = [];
    for (var i = 0; i < data['date_li'].length; i++) {
        d = data['date_li'][i]
        echartsDate.push(d);
    }
    option = {
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'cross'
            }
        },
        grid: {
            show: true,//是否显示直角坐标系的网格,true显示,false不显示
            left: '5%',//grid组件离容器左侧的距离
            containLabel: true,//grid 区域是否包含坐标轴的刻度标签,在无法确定坐标轴标签的宽度,容器有比较小无法预留较多空间的时候,可以设为 true 防止标签溢出容器。
            // width: '360px',
            right:'0%',
            top:'10%',
        },


        xAxis: {
            type: 'category',
            height: '100px',
            splitLine: {
                lineStyle: {
                    type: 'dashed'
                }
            },
            data:echartsDate
        },
        yAxis: {
            type: 'value',
            min: 1.5,
            splitLine: {
                lineStyle: {
                    type: 'dashed'
                }
            },
            name: '平均价格/元',
            nameLocation: 'center',
            nameGap: 30

        },
        series: [{
            name: '分散值(实际值)',
            type: 'scatter',
            label: {
                emphasis: {
                    show: true,
                    position: 'left',
                    textStyle: {
                        color: 'blue',
                        fontSize: 12
                    }
                }
            },
            data: data['data-predict']
        }, {
            name: '线性值(预测值)',
            type: 'line',
            showSymbol: false,
            data: myRegression.points,
            markPoint: {
                itemStyle: {
                    normal: {
                        color: 'transparent'
                    }
                },
                label: {
                    normal: {
                        show: true,
                        position: 'left',
                        formatter: myRegression.expression,
                        textStyle: {
                            color: '#333',
                            fontSize: 12
                        }
                    }
                },
                data: [{
                    coord: myRegression.points[myRegression.points.length - 1]
                }]
            }
        }]
    };
    center1.setOption(option, true);

}

设置完getdata1()函数后,还需要对detail_page.html文件中设置散点图标题的HTML代码进行修改,使该标题中的街道名称跟随房源数据进行同步修改。

<!--line-->
<div class="col-lg-12 col-md-12 mx-auto attribute-header">
    <h4><i class="fa fa-align-right" aria-hidden="true"></i>&nbsp&nbsp{{ house.block }}
        价格走势</h4>
    <div class="attribute-header-tip-line">
        <span>人工智能算法,为您预测房价走势</span>
    </div>
</div>

重启服务器,刷新当前页面,查看效果

9.7 本章小结

本章讲解了智能租房项目详情页的相关功能,包括详情页房源数据展示、户型占比可视化、小区房源数量TOP20可视化、户型价格走势可视化、预测房价走势可视化。通过学习本章的内容,希望读者能够掌握详情页模块的功能逻辑,并实现相关功能。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/382418.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年 前端JavaScript入门到精通 第一天

主要讲解JavaScript核心知识&#xff0c;包含最新ES6语法&#xff0c;从基础到API再到高级。让你一边学习一边练习&#xff0c;重点知识及时实践&#xff0c;同时每天安排大量作业&#xff0c;加深记忆&#xff0c;巩固学习成果。 1.1 基本软件与准备工作 1.2 JavaScript 案例 …

前端开发_AJAX基本使用

AJAX概念 AJAX是异步的JavaScript和XML(Asynchronous JavaScript And XML)。 简单点说&#xff0c;就是使用XMLHttpRequest对象与服务器通信。 它可以使用JSON&#xff0c;XML&#xff0c;HTML和text文本等格式发送和接收数据。 AJAX最吸引人的就是它的“异步"特性&am…

python-分享篇-GUI界面开发-PyQt5-对QListWidget表格进行数据绑定

代码 # -*- coding: utf-8 -*-# Form implementation generated from reading ui file bindtable.ui # # Created by: PyQt5 UI code generator 5.11.3 # # WARNING! All changes made in this file will be lost! 对QTableWidget表格进行数据绑定from PyQt5 import QtCore, Q…

Wireshark不显示Thrift协议

使用Wireshark对thrift协议进行抓包&#xff0c;但是只显示了传输层的tcp协议&#xff1a; "右键" -> "Decode As" 选择thrift的tcp端口 将“当前”修改为Thrift&#xff0c;然后点击“确定” 设置后&#xff0c;可以发现Wireshark里面显示的协议从Tcp变…

正版软件 - Proxyman:让网络调试变得更智能、更高效

在软件开发的世界里&#xff0c;网络调试一直是开发者和测试工程师的痛点。传统的调试工具往往操作复杂&#xff0c;界面不够直观&#xff0c;而且性能上也难以满足现代应用的需求。今天&#xff0c;我要向大家介绍一款名为Proxyman的网络调试工具&#xff0c;它以其简洁的界面…

第十八篇【传奇开心果短博文系列】Python的OpenCV库技术点案例示例:图像修复和恢复

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例系列短博文目录前言一、常用的图像修复与恢复技术二、插值方法示例代码三、基于纹理合成的方法示例代码四、基于边缘保持的方法示例代码五、基于图像修复模型的方法示例代码六、基于深度学习的方法示例代码七…

Vulnhub靶机:hacksudo-search

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;hacksudo-search&#xff08;10.0.2.50&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://download.vulnhub.co…

【leetcode热题100】 格雷编码

n 位格雷码序列 是一个由 2n 个整数组成的序列&#xff0c;其中&#xff1a; 每个整数都在范围 [0, 2n - 1] 内&#xff08;含 0 和 2n - 1&#xff09;第一个整数是 0一个整数在序列中出现 不超过一次每对 相邻 整数的二进制表示 恰好一位不同 &#xff0c;且第一个 和 最后一…

c语言游戏实战(4):人生重开模拟器

前言&#xff1a; 人生重开模拟器是前段时间非常火的一个小游戏&#xff0c;接下来我们将一起学习使用c语言写一个简易版的人生重开模拟器。 网页版游戏&#xff1a; 人生重开模拟器 (ytecn.com) 1.实现一个简化版的人生重开模拟器 &#xff08;1&#xff09; 游戏开始的时…

Python算法题集_随机链表的复制

Python算法题集_随机链表的复制 题138&#xff1a;随机链表的复制1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【双层循环】2) 改进版一【字典哈希】3) 改进版二【单层哈希】4) 改进版三【递归大法】 4. 最优算法 本文为Python算法题集之一的…

Python 错误及其解决方法

Python 是一种易于学习的编程语言&#xff0c;但初学者在学习和使用 Python 的过程中难免会遇到一些错误。以下是一些常见的 Python 错误及其解决方法&#xff1a; 1. 语法错误&#xff08;SyntaxError&#xff09;&#xff1a; python # 错误示例 print("Hello, World!…

elasticsearch下载及可视化工具下载使用

elasticsearch下载及配置、启动 一、下载 Download Elasticsearch | Elastic 二、启动 双击bat即可。 出现如下说明启动成功&#xff1a; 访问测试&#xff1a; 三、注意 &#xff08;1&#xff09;因为es启动默认端口是&#xff1a;9200,所以需要检查此端口是否被占用。…

js中bind、call、apply 区别(如何实现)

文章目录 一、作用二、区别applycallbind小结 三、实现 一、作用 call、apply、bind作用是改变函数执行时的上下文&#xff0c;简而言之就是改变函数运行时的this指向 那么什么情况下需要改变this的指向呢&#xff1f;下面举个例子 var name "lucy"; var obj {n…

python统计分析——两样本t检验

参考资料&#xff1a;用python动手学统计学 1、导入库 # 导入库 # 用于数值计算的库 import numpy as np import pandas as pd import scipy as sp from scipy import stats # 用于绘图的库 from matplotlib import pyplot as plt import seaborn as sns sns.set() 2、准备数…

【HTTP】localhost和127.0.0.1的区别是什么?

目录 localhost是什么呢&#xff1f; 从域名到程序 localhost和127.0.0.1的区别是什么&#xff1f; 域名的等级划分 多网站共用一个IP和端口 私有IP地址 IPv6 今天在网上逛的时候看到一个问题&#xff0c;没想到大家讨论的很热烈&#xff0c;就是标题中这个&#xff1a; …

基于微信小程序的校园故障维修管理系统的研究与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

闭区间上连续函数的性质【高数笔记】

1. 分几个性质 2. 每个性质的注意事项是什么 3. 每个性质适用什么类型的题型 4. 注意最值定理和正弦函数的不同 5. 做题步骤是什么

使用Docker本地部署Jupyter Notebook并结合内网穿透实现远程访问

文章目录 1. 选择与拉取镜像2. 创建容器3. 访问Jupyter工作台4. 远程访问Jupyter工作台4.1 内网穿透工具安装4.2 创建远程连接公网地址4.3 使用固定二级子域名地址远程访问 本文主要介绍如何在Ubuntu系统中使用Docker本地部署Jupyter Notebook&#xff0c;并结合cpolar内网穿透…

异地的文件如何共享?

在现代工作中&#xff0c;随着互联网的普及和信息化的进步&#xff0c;越来越多的人需要在异地进行文件共享和协同办公。无论是企业的异地分支机构&#xff0c;还是多个个人之间的跨地合作&#xff0c;通过异地的文件共享可以极大地提高工作效率和协作效果。本文将介绍一种名为…

C# CAD交互界面-自定义面板集-添加快捷命令(五)

运行环境 vs2022 c# cad2016 调试成功 一、引用 using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.Windows; using System; using System.Drawing; using System.Windows.Forms; 二、代码说明 [CommandMethod("Cre…