一文过pandas入门(前篇)——才疏学浅的莫笑天

2023-11-30 21:20

本文主要是介绍一文过pandas入门(前篇)——才疏学浅的莫笑天,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前篇主要是讲明pandas到底是什么,pandas中的常用的基本数据方法,数据交互方式,需要注意的踩坑点等。
之前整理了numpy部分的知识点,numpy的numpy.array对于python的数组运算做了很大贡献,使得python程序员可以用接近c语言的速度对矩阵数据进行运算吗,这对于后置的机器学习模块包括深度学习模块的贡献非常大,但对于数据分析来说,例如一个excel文件中,它并不是单独的包含一个数值类型,之前在读取csv文件的时候,显然里面包含时间序列,姓名等字符串序列,那numpy显然在很多时候并不适用于这种场景,而pandas模块应运而生。

安装方式

1. anconda自带
2. pip install pandas -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com/

什么是pandas

Pandas是一个强大的分析结构化数据的工具集,基于NumPy构建,提供了高级数据结构和数据操作工具,它是使Python成为强大而高效的数据分析环境的重要因素之一。

pandas的特点:

  1. 基于numpy构建,这一点非常关键,一个模块基于另一个模块构建的话,我们就需要注意在更新版本的时候一定要注重他们之间的版本关联性。
  2. 由于基于numpy,因此它继承了numpy对于矩阵数据的高性能运算。
  3. 提供了大量快速且便捷处理数据的方法。
  4. 常用于数据挖掘与数据分析
  5. 提供了数据分析的功能。
# daraframe对象与series对象
import pandas as pd
import numpy as np
ser_obj = pd.Series(range(1,6), index = ['a', 'b', 'c', 'd', 'e'])
df_obj = pd.DataFrame(range(5), index = ['a', 'b', 'c', 'd', 'e'])
print(df_obj)
# 它的索引是一个对象,而且这个对象是不可更改的,这样直接的保证了数据的安全性。
print(df_obj.index)
# 尝试打印索引数据,可以观察到是可以打印索引数据的。
print(df_obj.index[0])
# 尝试改变索引数据
try:df_obj.index[0] = 'e'print('改变成功')
except Exception as e:print(e)print('改变失败')
# 可以观察到这里是直接改变失败的,一旦数据构造完毕,就不能随便去更改它的索引了。
# head属性可以查看dataframe对象的头部信息。
print(df_obj.head())
   0
a  0
b  1
c  2
d  3
e  4
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
a
Index does not support mutable operations
改变失败0
a  0
b  1
c  2
d  3
e  4
# 查看数据
print(ser_obj)
# 查看数据,显然series数据类型,可以通过head名与下标进行访问。
print(ser_obj['a'])
print(ser_obj[0])
a    1
b    2
c    3
d    4
e    5
dtype: int64
1
1
# 切片索引,这里可以观察到切片数据是带有索引的。
print(ser_obj[1:3])
# 换一种方法,结果是一样的。
print(ser_obj['a':'c'])
# 索引也可以是不连续的,这是类似于list和numpy.array对象的。
print(ser_obj[[0, 2, 4]])
# 同样有基于numpy.array的布尔索引。
print(ser_obj[ser_obj>2])
print(ser_obj>2)
b    2
c    3
dtype: int64
a    1
b    2
c    3
dtype: int64
a    1
c    3
e    5
dtype: int64
c    3
d    4
e    5
dtype: int64
a    False
b    False
c     True
d     True
e     True
dtype: bool

Dataframe数据对象的索引与series是不同的!!!

# 构建一个dataframe数据
df_obj = pd.DataFrame(np.random.randn(5,4), columns = ['a', 'b', 'c', 'd'])
df_obj
abcd
00.297178-0.064710-1.945154-0.178690
11.496176-0.3690951.124525-0.913287
20.341343-1.080503-0.4788560.418045
30.743871-0.0245570.241574-0.192667
41.1566500.5496550.481397-0.812955
# 尝试根据索引去打印
print(df_obj['a'])
# 换一种方法
try:print(df_obj[1])print('打印成功')
except Exception as e:print(e)print('显然出错')
0    0.297178
1    1.496176
2    0.341343
3    0.743871
4    1.156650
Name: a, dtype: float64
1
显然出错

讲到这里似乎,dataframe和series并没有什么联系呀,其实不然,两个数据结构放到一块是有原因的,上面那个代码块报错了,那么如果我们换一种写法,如下所示:

print(df_obj['a'][1])
1.4961760531418462
# 显然,这并没有报错,那么在考虑到先前series数据
ser_obj = pd.Series(range(1,6), index = ['a', 'b', 'c', 'd', 'e'])
print(ser_obj[1])
2

series数据类型支持这种写法,提取出dataframe一个column也支持这种写法,答案显然已经呼之欲出了。
一个series对象其实就等于datafram的一个column,或者说,dataframe就是由一堆series组成的。

# 那么dataframe同样支出不连续的索引。
df_obj = pd.DataFrame(np.random.randn(5,4), columns = ['a', 'b', 'c', 'd'])
print(df_obj)
print(df_obj[['a','c']])
          a         b         c         d
0  0.173106  0.199585 -0.292090 -0.324643
1  0.382805  0.207301  1.874807 -0.716093
2  0.606986 -0.370534  1.616169 -1.343322
3 -0.428249 -0.397338 -1.001129 -0.796805
4  2.070116 -0.709817 -1.543283 -0.239500a         c
0  0.173106 -0.292090
1  0.382805  1.874807
2  0.606986  1.616169
3 -0.428249 -1.001129
4  2.070116 -1.543283

高级索引是pandas最重要的索引方式,也是我们能灵活利用pandas的核心技能。必修

  1. loc:基于列名的索引方式
  2. iloc:基于列索引id的索引方式
  3. ix结合12的索引方式
print(df_obj)
          a         b         c         d
0  0.173106  0.199585 -0.292090 -0.324643
1  0.382805  0.207301  1.874807 -0.716093
2  0.606986 -0.370534  1.616169 -1.343322
3 -0.428249 -0.397338 -1.001129 -0.796805
4  2.070116 -0.709817 -1.543283 -0.239500
# 前为行数,后为列数,核心是这四种用法,掌握应该就差不多了。
print(df_obj.loc[0:3, 'a':'c'])
print(df_obj.loc[0:3, 'a'])
print(df_obj.loc[0:3, ['a','b']])
print(df_obj.loc[[0,3], ['a','b']])
          a         b         c
0  0.173106  0.199585 -0.292090
1  0.382805  0.207301  1.874807
2  0.606986 -0.370534  1.616169
3 -0.428249 -0.397338 -1.001129
0    0.173106
1    0.382805
2    0.606986
3   -0.428249
Name: a, dtype: float64a         b
0  0.173106  0.199585
1  0.382805  0.207301
2  0.606986 -0.370534
3 -0.428249 -0.397338a         b
0  0.173106  0.199585
3 -0.428249 -0.397338
# 可以看到iloc取用下标为01对应了之前的abc
print(df_obj.iloc[0:3, 0:3])
          a         b         c
0  0.173106  0.199585 -0.292090
1  0.382805  0.207301  1.874807
2  0.606986 -0.370534  1.616169

在现版本中,ix已经被舍弃了,如果输出的话,可以看到报错信息,其实我也觉得ix挺鸡肋的。哈哈哈。

s1 = pd.Series(range(10, 20), index = range(10))
s2 = pd.Series(range(20, 25), index = range(5))print('s1: ' )
print(s1)print('') print('s2: ')
print(s2)# 经过分析得到无数据+有数据为NAN
print(s1 + s2)
s1: 
0    10
1    11
2    12
3    13
4    14
5    15
6    16
7    17
8    18
9    19
dtype: int64s2: 
0    20
1    21
2    22
3    23
4    24
dtype: int64
0    30.0
1    32.0
2    34.0
3    36.0
4    38.0
5     NaN
6     NaN
7     NaN
8     NaN
9     NaN
dtype: float64
df1 = pd.DataFrame(np.ones((2,2)), columns = ['a', 'b'])
df2 = pd.DataFrame(np.ones((3,3)), columns = ['a', 'b', 'c'])print('df1: ')
print(df1)print('') 
print('df2: ')
print(df2)print('df1+df2 = \n', df1+df2)
df1: a    b
0  1.0  1.0
1  1.0  1.0df2: a    b    c
0  1.0  1.0  1.0
1  1.0  1.0  1.0
2  1.0  1.0  1.0
df1+df2 = a    b   c
0  2.0  2.0 NaN
1  2.0  2.0 NaN
2  NaN  NaN NaN

观察上面部分的结果,我们来看,不论是series还是datafram,都可以抽象到表格水平进行计算。

pandas的层级索引

import pandas as pd
import numpy as npser_obj = pd.Series(np.random.randn(12),index=[['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd'],[0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]])
print(ser_obj)
a  0    0.3040681   -1.1097022   -0.070409
b  0   -1.3445121    1.0133832   -0.208385
c  0    1.4439181   -0.3666392    0.016325
d  0    1.1721331   -0.2604862   -0.231875
dtype: float64
print(ser_obj['a'][0])
0.3040683834625479
print(ser_obj.index)
MultiIndex([('a', 0),('a', 1),('a', 2),('b', 0),('b', 1),('b', 2),('c', 0),('c', 1),('c', 2),('d', 0),('d', 1),('d', 2)],)

可以根据切片的写法选择子集,或者根据索引的写法取得固定的值

## 验证子集是否可以遍历,如下证明,取出的属于可遍历的元素。
for ele in ser_obj['a']:print(ele)
0.3040683834625479
-1.1097020808798908
-0.07040932160774192
## 根据子集推论,整体也可以遍历。
for i in ser_obj:print(i)
0.3040683834625479
-1.1097020808798908
-0.07040932160774192
-1.3445122594840375
1.0133826686923877
-0.20838546585243495
1.4439176185886589
-0.3666390729905095
0.016324813488977796
1.172133369304898
-0.26048645446327456
-0.23187463237829625
## 从如上两个遍历的结果分析,series类是可遍历的数据,那么它的索引应该是这个类的其他某发个属性。

swaplevel()方法可以交换内外层索引顺序。

print(ser_obj.swaplevel())
0  a    0.304068
1  a   -1.109702
2  a   -0.070409
0  b   -1.344512
1  b    1.013383
2  b   -0.208385
0  c    1.443918
1  c   -0.366639
2  c    0.016325
0  d    1.172133
1  d   -0.260486
2  d   -0.231875
dtype: float64

利用pandas进行统计描述

## 先构建一个随机矩阵# 1. 构建一个随机数据,利用np.random.rand()
# 2. 利用pandas.DataFrame添加索引,生成对应的数据
# 3. 可利用lambda表达式+applymap()方法进行数据整理
## 让代码失去可读性……
df = pd.DataFrame(np.random.rand(3,3), columns=list('abc'), index=list('ABC')).applymap(lambda x: '%.2f'%x).astype(float)

几个关键的方法

  1. applymap():该方法即对dataframe类中每一个元素做一个函数映射,该函数就是我们要传入的参数。(这里我们传入的是lambda表达式,它可以快速构建简易的函数。)
  2. lambda表达式,构建简易函数的方法。但注意它的返回值是一个迭代对象,不可直接展示。如下模块可以该函数进行简单演示
  3. 代码杂糅,如上本人是一行写完的,首先,这样写并不会影响运行速度,甚至如果调用了生成式等写法,会比普通的循环还要快,但是这样写会极度影响可读性,在特殊情况下,比如我们非常不希望别人看懂我们的代码的情况下,可以选择10行,20行,甚至30行杂糅,这样杂糅起来分解会非常的麻烦。
test_func = lambda x:x+1
test_list = [ i for i in range(2,10,2)]
print(test_list)
map_list = map(test_func,test_list)
print(map_list)
print(list(map_list))
## lambda表达式在统计中会非常常用,与正则表达式同等重要。
[2, 4, 6, 8]
<map object at 0x00000235C2BACE80>
[3, 5, 7, 9]

我们知道常用的统计函数有,max,min,sum,mean之类的。pandas同样提供了这些方法。接下来让我们回到数据。

df
abc
A0.060.050.05
B0.810.900.89
C0.540.230.53
# 这是四种最基本的统计运算,同时很多运算也是基于此计算。
print(df['a'].sum())
print(df['a'].mean())
print(df['a'].max())
print(df['a'].min())
1.4100000000000001
0.47000000000000003
0.81
0.06

这些函数经过封装后,就包含了一些必要的参数。

# 默认情况。
print(df.sum())
# 加上参数:axis=1。
print(df.sum(axis=1))
# 默认情况取出每一个column数值的max。
print(df.idxmax())
# 加上参数
print(df.idxmax(axis=1))
a    1.41
b    1.18
c    1.47
dtype: float64
A    0.16
B    2.60
C    1.30
dtype: float64
a    B
b    B
c    B
dtype: object
A    a
B    b
C    a
dtype: object

而事实上,axis也不是唯一的参数,对于特殊的数据nan,也有针对与它的方法。

df = pd.DataFrame(np.random.rand(3,3), columns=list('abc'), index=list('ABC')).applymap(lambda x: '%.2f'%x).astype(float)
df['a']['A'] = np.NAN
print(df)
      a     b     c
A   NaN  0.01  0.80
B  0.03  0.47  0.88
C  0.88  0.44  0.63
## 在nan存在的情况下。计算某些值会受到阻塞,比如。
print(df['a'].mean())
## 这里注意,虽然mean中是没有填任何东西的,但是实际上对于我们可以使用numpy测试一下。
try:a = np.array([np.NAN, 1, 2])print(a.mean())print(a.sum())
except Exception as e:print(e)
## 可以看到输出的是nan,这是因为nan与任何数相加都得nan。那么为什么df中就没有出错呢。是因为里面有一个默认的参数为skip=True表示忽略NAN值。
## 如果取消它结果就会。如下
0.455
nan
nan
print(df['a'].mean(skipna=False))
## ok过。
nan
## describe方法
df.describe()
## 一般情况下我们需要加一个,否则describe只会描述数值列,字符串等,会忽略掉。
df.describe(include='all')
abc
count2.0000003.0000003.000000
mean0.4550000.3066670.770000
std0.6010410.2573580.127671
min0.0300000.0100000.630000
25%0.2425000.2250000.715000
50%0.4550000.4400000.800000
75%0.6675000.4550000.840000
max0.8800000.4700000.880000

其他常用方法

# 输出非nan的数量
print(df.count())
print(df.count(axis=1))
a    2
b    3
c    3
dtype: int64
A    2
B    3
C    3
dtype: int64
# 获取最大最小值索引,针对与series类数据。
print(df['a'].argmax())
print(df['a'].argmin())
2
1
# 获取最大最小值索引,针对与dataframe类数据
print(df.idxmax())
print(df.idxmin())
a    C
b    B
c    B
dtype: object
a    B
b    A
c    C
dtype: object
# 算术中位数
print(df.median())
a    0.455
b    0.440
c    0.800
dtype: float64

pandas的文件操作

  • 如基于numpy的文件操作一样,对于dataframe独特的结构来说,pandas也提供了独特的读取文件以及写入文件方式。
  • 我们只需要记住几种常用的读取方式
  1. read_csv()
  2. read_excel()
  3. read_html()
  4. read_json()
  5. read_sql()
# 上面的方法,因常用才需要说,csv和excel文件根本不用多说,常用于数据分析,数据处理。
# html以及json文件在web开发爬虫中也需要用到。
# sql必知必会,但是一般对于pandas来说,这个方法不太常用。
# 读取csv文件,直接调用read_csv的方法。
data = pd.read_csv('student_info.csv')
# 这里获取到文件直接就是我们需要的dataframe,当然,通常情况下可能在读取文件时,会报错,此时我们需要调整参数,核心的几个参数:
"""
1. encoding='':pd.read_csv('student_info.csv', encoding='utf-8')指定以utf-8的编码方式读取。
2. engine='python':可以解决路径中含有中文的问题。
3. usecols=[]:读取指定列数。
"""
"\n1. encoding='':pd.read_csv('student_info.csv', encoding='utf-8')指定以utf-8的编码方式读取。\n2. engine='python':可以解决路径中含有中文的问题。\n3. usecols=[]:读取指定列数。\n"
## 写入csv文件在dataframe中有其方法:to_csv
df = pd.DataFrame(np.random.rand(3,3), columns=list('abc'), index=list('ABC')).applymap(lambda x: '%.2f'%x).astype(float)
print(df)
      a     b     c
A  0.24  0.73  0.77
B  0.59  0.15  0.09
C  0.02  0.01  0.79
## 就需要一句代码即可。这看上去确实要比基本的python操作方便很多。
df.to_csv('write_csv_1.csv')

在这里插入图片描述

## 或者你可以选择牺牲可读性。效果相同。
pd.DataFrame(np.random.rand(3,3), columns=list('abc'), index=list('ABC')).applymap(lambda x: '%.2f'%x).astype(float).to_csv('write_csv_2.csv')

当然事实上to_csv是有很多可选参数的。

  1. file_path:也就是写入文件的路径了。
  2. sep:值数据的分隔符,分割符要放到下一个代码块中单独说一下。如下。这里默认为’,’
  3. columns:指定保存的列,我们可以指定写入列,而非全部列。
  4. header:是否需要列名。默认为True
  5. index:是否保留行索引。默认为True
  6. na_rep:指定字符代替nan。默认为空字符
  7. mode:方式,默认为w,可指定为a,即文件的操作参数,写入,与追加。

说到这个sep,我们就必须知道csv文件是什么,在上一篇博客中我提到过,csv文件就是纯文本文件,那么利用记事本将csv文件打开,如下:
在这里插入图片描述

可以看到这里的逗号’,‘其实就是数据分隔符的意思,它将数据分为条条框框成一个表格。如果没有我们将它的分隔符设置为其他。那么整体数据就会发生变化。
对于pandas读取csv文件来说,默认为’,'这是一般情况下认定的一个标准格式,如果转来转去,那得有多麻烦。

数据库交互

# 数据库作为就业的必知必会,基础内容很多人就算没有很会,也多少知道一点。这里直接讲方法了。# 导入必要的模块
# panda负责处理数据
import pandas as pd
# create_engine负责对数据库进行连接
from sqlalchemy import create_engine# 在此之前,必须要先创建对应的数据库。
# 测试连接
try:engine = create_engine('mysql+pymysql://root:root@localhost:3306/db2')
except Exception as e:print(e)
# 创建数据库语句
sql_op = """select * from test_table;
"""
# 上一步没有报错信息说明连接成功。
# 这里对于pandas.read_csv是可以加入数据库语句的,这提供了很大的操作性。
df = pd.read_sql(sql_op,engine)
print(df)
Empty DataFrame
Columns: [id, name]
Index: []
## 如上所示,我们可以获取到数据,这里没有是因为我这里仅仅是创建了一个表。。。它确实没有数据,不过我们可以写入呀。
df = pd.DataFrame({'id':[1,2,3,4],'name':[34,56,78,90]})
print(df)
try:df.to_sql('df',engine=engine, index=False)
except Exception as e:print(e)
# 这里是报错了,显然,只是我将它的报错信息打印出来了,try和except是抛出异常语句,这种语句在任何一种高级语言都非常重要,属于基本功,其实就是
# 先try,如果报错,将采取什么措施,最后还有一个finally,只是我一般不咋用,finally表示最终做什么操作,整体格式如下:
"""
try:step_1_1
except Exception as e:step_1_2
finally:step_2
"""
   id  name
0   1    34
1   2    56
2   3    78
3   4    90
to_sql() got an unexpected keyword argument 'engine''\ntry:\n    step_1_1\nexcept Exception as e:\n    step_1_2\nfinally:\n    step_2\n'
## 接下来排错,修改bug。根据错误信息提示,该方法没有这个关键字,那么这时候要考虑几点。
# 1. 方法更新确实没有了这个参数有了其他参数替代。
# 2. 格式写错了。
# 3. 参数拼错了。
df.to_sql('df_1',engine, index=False)
# 这里显然,哈哈哈,格式写错了,笑死了。加了个engine=

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p6gYRE4R-1637411323180)(attachment:%E5%9B%BE%E7%89%87.png)]

保证以上内容全部过一遍,pandas差不多就过了,入门,下一篇,我会从分享两个非常重要的内容,一个是数据处理的一个重要步骤,一个是一种独特且非常常用的数据类型。感谢关注——才疏学浅的莫笑天

在这里插入图片描述
也欢迎同喜欢研究学习python的小伙伴添加博主微信哦。

这篇关于一文过pandas入门(前篇)——才疏学浅的莫笑天的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/438602

相关文章

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

在Pandas中进行数据重命名的方法示例

《在Pandas中进行数据重命名的方法示例》Pandas作为Python中最流行的数据处理库,提供了强大的数据操作功能,其中数据重命名是常见且基础的操作之一,本文将通过简洁明了的讲解和丰富的代码示例,... 目录一、引言二、Pandas rename方法简介三、列名重命名3.1 使用字典进行列名重命名3.编

Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南

《Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南》在日常数据处理工作中,我们经常需要将不同Excel文档中的数据整合到一个新的DataFrame中,以便进行进一步... 目录一、准备工作二、读取Excel文件三、数据叠加四、处理重复数据(可选)五、保存新DataFram

一文带你搞懂Nginx中的配置文件

《一文带你搞懂Nginx中的配置文件》Nginx(发音为“engine-x”)是一款高性能的Web服务器、反向代理服务器和负载均衡器,广泛应用于全球各类网站和应用中,下面就跟随小编一起来了解下如何... 目录摘要一、Nginx 配置文件结构概述二、全局配置(Global Configuration)1. w

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

pandas数据过滤

Pandas 数据过滤方法 Pandas 提供了多种方法来过滤数据,可以根据不同的条件进行筛选。以下是一些常见的 Pandas 数据过滤方法,结合实例进行讲解,希望能帮你快速理解。 1. 基于条件筛选行 可以使用布尔索引来根据条件过滤行。 import pandas as pd# 创建示例数据data = {'Name': ['Alice', 'Bob', 'Charlie', 'Dav

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联