Intern assignment

本文介绍了一种使用Python和PyQt5实现的城市温度数据处理和可视化方法,包括数据读取、处理、展示及用户交互功能。

Introduction

Programming environment

programming language: python 3.7
OS: Mac OS
Main Package : PyQt5, csv, matplotlib

Project structure

dataset

数据集是一个csv文件,总共有14个城市,也就是14个csv文件。文件样例是
文件样式
前十行是城市的具体信息,从11行开始,第一列是时间信息,第二列是温度信息。时间信息格式为YYYYMMDDTHHmm。就是年月日小时分钟,中间用T做分隔符。

数据处理部分

用python 的csv包进行处理,主要包括三个函数,读取路径下所有csv文件。做法:

def listdir(path, list_name):
	for file in os.listdir(path):
		file_path = os.path.join(path, file)
		if(os.path.splitext(file_path)[1] == '.csv'):
			list_name.append(file_path)

然后就是从csv中读取city name 和关于city的data。我将city的信息和气温信息放在了一起,后期可以分开。

def getData(file, cityName, city_data):
	csvFile = open(file, "r")
	reader = csv.reader(csvFile)
	result = {}
	for item in reader:
		if reader.line_num == 1:
			city = item[1]
			cityName.append(item[1])
			result[item[0]] = item[1]
		else:
			result[item[0]] = item[1]

	city_data[city] = result
	csvFile.close()

Project Goal

  1. 展现city name的list,并让用户能逆序和顺序展示city list。
  2. 通过选择城市,能展示该城市的温度随时间变化曲线。并要求能满足多城市的情况,也就是同时展现多个城市的曲线。
  3. 能让用户设置阈值,来展现城市温度曲线中,小于阈值的点。

UI design

I use PyQt to design the interface. 一开始因为PyQt5在mac环境下没有qt designer。一开始我尝试了qt for designer 也就是pyside2,但那个界面并不是很友好而且我mac安装pyside2-tools有问题,所以我用了qt creator。
主要用到的控件是:
Label 标签
PushButton 按钮
TextBrowser 文本框
Graphics view 图片显示
Scroll area 滚动区域
checkbox 多选框
line edit 可编辑文本框(单行)
这是成图。
在这里插入图片描述
在ui设计完之后,我们在QT Creator中得到的是一个.ui文件,我们需要用

pyuic5 -o fileName.py fileName.ui

指令来将ui文件转换为py文件,以方便后续逻辑的设计。
界面和业务逻辑设计要分开。所以UI界面单独做一个文件,业务逻辑文件单独拎出来,import UI文件就可。

控件说明和用法:

控件的创建在qt creator中比较简单,就是直接拖拽组件到mainframe中即可。在右边窗口要记得定义好组件的名字,在之后的代码设计逻辑时要用到,不然指向会不明确。

LIst View:

QList_View的信号有两种,clicked和doubleClicked 分别表示单击和双击时触发。

setModel方法可以用来设置View所关联的模型,可以使用Python原生的list作为数据源model

 #实例化列表模型,添加数据
 slm=QStringListModel()
 self.qList=['Item 1','Item 2','Item 3','Item 4']

 #设置模型列表视图,加载数据列表
 slm.setStringList(self.qList)

 #设置列表视图的模型
 listview.setModel(slm)

 #单击触发自定义的槽函数
 listview.clicked.connect(self.clicked)

参考:

https://blog.youkuaiyun.com/jia666666/article/details/81624550

QPushButton

有四种信号

void clicked ( bool checked = false )
void pressed ()
void released ()
void toggled ( bool checked )

信号用法

QPushButton.clicked.connect(function)
这样信号就连接了一个function,如果点击按钮,就会检测到信号,并触发函数function.

https://blog.youkuaiyun.com/qter_wd007/article/details/6618386

QLineEdit

作为单行文本框控件,可以编辑单行字符串,用于接受用户输入,与QTextEdit不同,QTextEdit是多行文本框控件。
用法:
QLineEdit.text()就能获得文本,返回字符串。如果没有输入的时候,返回""

https://blog.51cto.com/9291927/2422601

QCheckBox

这里用了遍历CheckBox的方法:

for i in range(1, 14):
	temp = getattr(self, "checkBox_%d"%i)
	if temp.checkState():
		checkbox_state.append(1)
		num = num + 1
	else:
		checkbox_state.append(0)

temp代表每个CheckBox,然后用内置函数.checkState()返回是否选中。

内置方法:

设置复选框的状态
setChecked()

返回复选框的显示文本
text()

检查复选框是否被选中
isChecked()

https://blog.youkuaiyun.com/jia666666/article/details/81533763

ScrollArea用法

作为一个容器,可以将一些Widget放入其中,然后进行管理。他是一个可以滚动查看的一个容器。但是内容都是放在他下面的scrollAreaWidgetContents中的。

TextBrowser

TextBrowser用法
TextBrowser.setText()和TextBrowser.clear()

Graphics View

https://zhuanlan.zhihu.com/p/26379590
https://whuhan2013.github.io/blog/2017/03/28/pyqt-matplotlib-learn/
https://my.oschina.net/u/4275236/blog/3354339
https://www.coder.work/article/4956997

这个部分主要与Matplotlib结合,放在下一部分说明。

matplotlib作图部分

结合PyQt5 和matPlotLib

首先关于matplotlib要引用的包有

import matplotlib
matplotlib.use("Qt5Agg")  # 声明使用QT5
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

这里用matplotlib.backends.backend_qt5agg来连接PyQt5

class Figure_Canvas(FigureCanvas):
	def __init__(self, parent=None, width=11, height=11, dpi=70): 
		fig = Figure(figsize=(width, height), dpi=70)

		FigureCanvas.__init__(self,fig)
		self.setParent(parent)

		self.ax = fig.add_subplot(111)

	def generate(self, xs, ys, num, c, thresold=''):
		if(num == 1):
			self.ax.cla()
			self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y%m%dT%H%M'))
			self.ax.xaxis.set_major_locator(mdates.DayLocator())
			x = range(len(xs))
			self.ax.set_title('Temperature over time of %s'%c[0])
			self.ax.set_xlabel('Time')
			self.ax.set_ylabel('Temperature(F)')
			self.ax.set_xticks(x[::8])
			self.ax.set_xticklabels(xs[::8], rotation=70)
			self.ax.plot(x, ys, label='%s'%c[0])
			self.ax.legend(loc='upper right')
			if thresold is not '':
				x, y_sc = self.pointsFilter(thresold, xs, ys, num)
				self.ax.scatter(x, y_sc, s=40, alpha=0.7)

		else:
			self.ax.cla()
			self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y%m%dT%H%M'))
			self.ax.xaxis.set_major_locator(mdates.DayLocator())
			x = range(len(xs[0]))
			self.ax.set_title('Temperature trend among multiple cities')
			self.ax.set_xlabel('Time')
			self.ax.set_ylabel('Temperature(F)')
			self.ax.set_xticks(x[::8])
			self.ax.set_xticklabels(xs[0][::8], rotation=70)
			for i in range(num):
				self.ax.plot(x, ys[i], label='%s'%c[i])
			self.ax.legend(loc='upper right')
			if thresold is not '':
				x, y_sc = self.pointsFilter(thresold, xs, ys, num)
				for i in range(num):
					self.ax.scatter(x[i], y_sc[i], s=40, alpha=0.7)


	def pointsFilter(self, thresold, xs, ys, num):
		x_sc = []
		y_sc = []
		if (num == 1):
			for i in range(len(ys)):
				if ys[i] < float(thresold):
					y_sc.append(ys[i])
					x_sc.append(i)
		else:
			for i in range(num):
				x_tmp = []
				y_tmp = []
				for j in range(len(ys[i])):
					if ys[i][j] < float(thresold):
						x_tmp.append(j)
						y_tmp.append(ys[i][j])
				x_sc.append(x_tmp)
				y_sc.append(y_tmp)
		return x_sc, y_sc

这里初始化fig为Figure(figsize=(width, height), dpi=70)。dpi是分辨率。宽高这种都可以调整来适应放入的graphics view。而且这个figure并不是pyplot下的。
通过继承FigureCanvas类,使得该类既是一个PyQt5的Qwidget,又是一个matplotlib的FigureCanvas,这是连接pyqt5与matplotlib的关键。

class Mytest(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 设置窗口标题
        self.setWindowTitle('My First App')
        self.setFixedSize(800, 600)
       
        # ===通过graphicview来显示图形
        self.graphicview = QtWidgets.QGraphicsView()  # 第一步,创建一个QGraphicsView
        self.graphicview.setObjectName("graphicview")

        dr = Figure_Canvas()
        #实例化一个FigureCanvas
        dr.test()  # 画图
        graphicscene = QtWidgets.QGraphicsScene()  # 第三步,创建一个QGraphicsScene,因为加载的图形(FigureCanvas)不能直接放到graphicview控件中,必须先放到graphicScene,然后再把graphicscene放到graphicview中
        graphicscene.addWidget(dr)  # 第四步,把图形放到QGraphicsScene中,注意:图形是作为一个QWidget放到QGraphicsScene中的
        self.graphicview.setScene(graphicscene)  # 第五步,把QGraphicsScene放入QGraphicsView
        self.graphicview.show()  # 最后,调用show方法呈现图形!Voila!!
        self.setCentralWidget(self.graphicview)
        self.graphicview.setFixedSize(800,600)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mytest=Mytest()

    mytest.show()
    app.exec_()

作图问题

  1. 时间间隔作为横坐标
    因为时间间隔变量与整型浮点型等不同,所以需要进行转换才能使其作为横坐标。
    以下是例子,非正式代码内容
from datetime import datetime
import matplotlib.dates as mdates
import matplotlib.pyplot as plt

dates = ['2016010106','2016010107','2016010108','2016010109','2016010110','2016010111','2016010112','2016010113',
         '2016010114','2016010115','2016010116','2016010117','2016010118']
#把string格式的日期转换成datetime格式
xs = [datetime.strptime(d, '%Y%m%d%H') for d in dates]
ys = ['36','29','26','22','29','38','48','55','56','60','55','48','51']

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
#指定X轴的以日期格式(带小时)显示
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y%m%d%H'))
#X轴的间隔为小时
ax.xaxis.set_major_locator(mdates.HourLocator())
plt.plot(xs, ys)
plt.gcf().autofmt_xdate()  
plt.show()

这里用到一个函数datetime.strptime(),用这个函数需要from datetime import datetime.
这其实是对dates中的值进行遍历,然后通过strptime做转换,第二个参数需要对数据进行解读,比如说%Y%m%d%H就是代表年月日小时。
这里还要用到ax.xaxis.set_major_formatter(mdates.DateFormatter(’%Y%m%d%H’))来指定x轴的以日期格式显示。
然后设置x轴的间隔为小时。
ax.xaxis.set_major_locator(mdates.HourLocator())。
这里还对温度进行了小数点的截取。

for y in temperature[0]:
	ys.append(float(y.split('.')[0] + '.' + y.split('.')[1][:2]))

https://blog.youkuaiyun.com/shener_m/article/details/81047862

  1. 横坐标太过密集
    对于这个问题我用了
self.ax.set_xticks(x[::8])
self.ax.set_xticklabels(xs[::8], rotation=70)

横坐标实际用了x,也就是原来的横坐标的长度作为横坐标,因为原来的时间坐标,间隔也都是以同样长度,2小时为单位,就不会影响比例。然后通过选择每八个坐标选择一个显示,同时设置rotation旋转为70度,让坐标显示斜过来,以使坐标显示不重叠。

  1. axes作图与pyplot不同
    用法基本相同,除了函数名有些不同,在axes中都是set_xlabel之类,但pyplot中是plt.xlabel.

  2. 点波动过高,不够连续
    将x轴改为用时间序列的长度作为总长度,间隔为1的数列即可。

python 打包应用程序

最后python的打包方式,我用的是pyinstaller,经历了重重险阻才打包成可运行的程序,主要遇到两个问题:

  1. 打包好的程序运行不了,存在module not found的问题,提示pkg_resources.py2_warn。这个module not found。

解决方法是:
在.spec文件中,更改hiddenimports参数,加入"pkg_resources.py2_warn"

  1. 数据集无法使用。说是dataset not found
    这个问题困扰了很久,尝试的办法有1.更改.spec的data参数。修改的用法是,(原项目中资源文件路径,打包后路径),也就是["./dataset", “./dataset”],第二个尝试的办法,是在引用数据集的代码中添加如下代码,然后每次file_path传入的时候改为resource_path(file_path)。然后问题就得到了解决。
import os 
import sys
def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)

参考博客:

https://blog.youkuaiyun.com/m0_37477175/article/details/82146996
https://blog.youkuaiyun.com/weixin_42052836/article/details/82315118

代码:https://github.com/Terrencezwy/City-temperature

在 Java 中,`intern` 方法是 `String` 类中的一个 `native` 方法,与 Java 的字符串常量池紧密相关,有助于编写高效且节省内存的程序 [^1]。 ### 使用方式 在 Java 代码里,可直接对 `String` 对象调用 `intern` 方法,示例如下: ```java public class InternExample { public static void main(String[] args) { String s1 = new String("a"); String s2 = s1.intern(); String s3 = "a"; System.out.println(s2 == s3); // 输出 true } } ``` 上述代码中,`s1.intern()` 调用了 `intern` 方法,若常量池已有内容为 "a" 的字符串,就返回常量池中该字符串的引用 [^3]。 ### 原理 `intern` 方法的工作原理为检查常量池中是否存在相同内容的字符串,若存在则返回常量池中该字符串的引用;若不存在,则将当前字符串对象添加到常量池并返回其引用 [^1]。从底层来看,`intern()` 是由 C 语言实现的 `native` 底层方法,用于从 `String` 缓存池中获取一个与该字符串内容相同的字符串对象。当执行该方法时,若缓存池已有该 `String` 内容,就直接从缓存池中获取该 `String` 内容对象;若缓存池中没有该 `String` 内容对象,则把该 `String` 内容对象放到缓存池中,并返回这个字符串对象的引用 [^4]。 ### 相关知识 - **字符串常量池**:这是 Java 中用于存储字符串字面量的一个特殊内存区域。像 `String s = "hello";` 这样的字符串字面量会被直接存储在字符串常量池中。而 `new String("hello")` 会在堆内存中创建一个新的 `String` 对象,同时字符串字面量 "hello" 会隐式地被放入常量池 [^3]。 - **性能与内存优化**:使用 `intern` 方法可避免创建重复的字符串对象,从而节省内存。在处理大量重复字符串时,能显著提升性能。不过,过度使用 `intern` 方法可能会使常量池变得庞大,进而影响性能 [^1][^5]。 - **潜在陷阱**:在多线程环境下使用 `intern` 方法时,要注意线程安全问题。由于常量池是全局共享的,多个线程同时调用 `intern` 方法可能会引发竞争条件。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值