利用python进行数据分析——基本内容

本文介绍了如何在Python环境中使用Ipython和JupyterNotebook进行数据分析,包括基本操作、交互式命令、自省功能、引用和赋值的区别、以及Python中可迭代对象、迭代器、生成器和各种推导式的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里主要是对《利用python进行数据分析》的学习,原书的电子版地址为:

https://github.com/iamseancheney/python_for_data_analysis_2nd_chinese_version

不知道这个项目是不是译者或者是什么好心人整理的。

这里不准备对数据分析或者是使用python的背景这些东西做过多的解释,先来看下Ipython和jupyter。

Ipython和jupyter

在安装完python之后(不管是直接安装的某个版本的python,或者是通过anaconda进行安装),通过配置环境变量之后,都可以通过在终端或者cmd中输入python进入python环境:

C:\Users\xxx>python
Python 3.7.1 (default, Dec 10 2018, 22:54:23) [MSC v.1915 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

在上面的python环境中,可以进行交互式操作。而Ipython可以说是这个python的加强版,Ipython可以通过在终端或cmd中输入Ipython进入:

C:\Users\xxx>Ipython
Python 3.7.1 (default, Dec 10 2018, 22:54:23) [MSC v.1915 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]:

可以看出两者的界面是有所区别的。而jupyter则是一个更宽泛的多语言交互计算工具,现在Ipython可以作为jupyter使用的内核。

因为这里使用jupyter作为python代码运行的基础(即不是通过运行.py文件运行代码的),因此可以将jupyter看作是Ipython的进阶版。

目前jupyter可以用作编写、测试、调式python代码的增强shell,还可以使用jupyter编写markdown和HTML内容。

jupyter操作

运行jupyter notebook

在终端或cmd中执行jupyter notebook:

C:\Users\xxx>jupyter notebook
[I 15:15:17.002 NotebookApp] JupyterLab extension loaded from D:\Anaconda3\lib\site-packages\jupyterlab
[I 15:15:17.002 NotebookApp] JupyterLab application directory is D:\Anaconda3\share\jupyter\lab
[I 15:15:17.007 NotebookApp] Serving notebooks from local directory: C:\Users\wood
[I 15:15:17.007 NotebookApp] The Jupyter Notebook is running at:
[I 15:15:17.008 NotebookApp] http://localhost:8888/?token=7b8bd3e1540e6fdcc8afd7846a0ef12835889a04b01f69ff
[I 15:15:17.009 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 15:15:17.460 NotebookApp]

    To access the notebook, open this file in a browser:
        file:///C:/Users/xxx/AppData/Roaming/jupyter/runtime/nbserver-2252-open.html
    Or copy and paste one of these URLs:
        http://localhost:8888/?token=7b8bd3e1540e6fdcc8afd7846a0ef12835889a04b01f69ff

上面的命令会打开本地浏览器,然后可以选择对应的.ipynb文件打开。

或者按照上面的提示在启动notebook之后,手动输入本地网址打开文件。

自省

在变量前或者变量后使用问号?,可以显示对象的信息:

a = [1,2,3]

# ?a
a?

 结果为:

Type:        list
String form: [1, 2, 3]
Length:      3
Docstring:  
Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list.
The argument must be an iterable if specified.

而对于函数或者实例方法,则会显示定义过的文档字符串:

def func(a,b,c):
    """a:example
       b:example
       c:example"""
    print("hello world")

func?

结果为:

Signature: func(a, b, c)
Docstring:
a:example
b:example
c:example
File:      c:\users\xxx\desktop\python_demo\<ipython-input-48-07ebd1c164cf>
Type:      function

而两个问号??则会显示函数的内容:

def func(a,b,c):
    """a:example
       b:example
       c:example"""
    print("hello world")

func??

结果为:

Signature: func(a, b, c)
Source:   
def func(a,b,c):
    """a:example
       b:example
       c:example"""
    print("hello world")
File:      c:\users\xxx\desktop\python_demo\<ipython-input-49-2b7dcc8932bb>
Type:      function

同时问号?还可以匹配命名空间中的变量:

fun*?

结果为:

func

魔术命令

Ipython中的特殊命令(python中不存在)被称为魔术命令,格式为在命令前添加%前缀。

 %run

该命令可以在Ipython或jupyter中运行某个.py文件:

# test.py
print("hello world")

运行如下代码:

%run test.py

结果为:

hello world
%timeit

能够测量python语句的执行时间:

%timeit %run test.py

结果为: 

hello world
...
hello world
3.07 ms ± 518 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

即能够通过多次运行python语句,来测量其运行时间。

%pwd

表示当前目录

%matplotlib inline

在jupyter中表示可以在notebook中进行可视化显示。

python语法

这里直接跳过简单的内容,看一下可能会引起混淆的部分。

引用、赋值、浅拷贝、深拷贝

这里直接先给出具体的区别:

  • 引用:指向实际的内存单元,可以说变量名就是对其指向的实际的内存单元的引用
  • 赋值:变量间的赋值,指向的是同一块内存单元
  • 浅拷贝:只复制最外层的结构,对于内层的嵌套结构直接引用其原始的内存地址
  • 深拷贝:不可变类型直接引用原始的内存地址,可变类型重新创建

首先看不可变类型:

import copy

a = (1,2,3)
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

print(a,b,c,d,id(a),id(b),id(c),id(d))

结果为:

(1, 2, 3) (1, 2, 3) (1, 2, 3) (1, 2, 3) 1783431521840 1783431521840 1783431521840 1783431521840

可以看出指向的地址都一样,即引用的是同一块内存。

再看下可变类型:

a = [1,2,3]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

print(a,b,c,d,id(a),id(b),id(c),id(d))

a[1] = 5

print(a,b,c,d,id(a),id(b),id(c),id(d))

结果为:

[1, 2, 3] [1, 2, 3] [1, 2, 3] [1, 2, 3] 1783402930888 1783402930888 1783402930440 1783402930696
[1, 4, 3] [1, 4, 3] [1, 2, 3] [1, 2, 3] 1783402930888 1783402930888 1783402930440 1783402930696

即赋值会直接引用原始的内存地址,因此发生内容修改时,会一同发生变化。而对于非嵌套的数据结构,深拷贝和浅拷贝都会重新复制一份数据,因此不会发生更改。

对于可变类型的嵌套结构:

a = [1,2,[3,4]]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

print(a,b,c,d,id(a),id(b),id(c),id(d))

a[2][0] = 5

print(a,b,c,d,id(a),id(b),id(c),id(d))

结果为:

[1, 2, [3, 4]] [1, 2, [3, 4]] [1, 2, [3, 4]] [1, 2, [3, 4]] 1783402932232 1783402932232 1783402930824 1783402930888
[1, 2, [5, 4]] [1, 2, [5, 4]] [1, 2, [5, 4]] [1, 2, [3, 4]] 1783402932232 1783402932232 1783402930824 1783402930888

和之前有区别的在于,嵌套结构的内层数据在浅拷贝时不会发生创建,而是会引用原有的内存地址。

对于不可变类型的嵌套结构来说,因为内外都是不可变类型的,因此地址都是相同的。

对于可变地址和不可变地址的嵌套结构来说:

a = [1,2,(3,4)]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

print(a,b,c,d,id(a),id(b),id(c),id(d))
print(a,b,c,d,id(a[2]),id(b[2]),id(c[2]),id(d[2]))

a[0] = 5

print(a,b,c,d,id(a),id(b),id(c),id(d))
print(a,b,c,d,id(a[2]),id(b[2]),id(c[2]),id(d[2]))

结果为:

[1, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] 1783402914248 1783402914248 1783402914184 1783402914376
[1, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] 1783431576392 1783431576392 1783431576392 1783431576392
[5, 2, (3, 4)] [5, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] 1783402914248 1783402914248 1783402914184 1783402914376
[5, 2, (3, 4)] [5, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] 1783431576392 1783431576392 1783431576392 1783431576392

这里我们一行一行看:

  • 第一行:因为外层是可变类型,因此在浅拷贝时(只拷贝外层)由于重新创建,地址发生了变化,深拷贝(拷贝外层和内层)也会发生变化
  • 第二行:但是内层由于是不可变类型,因此地址不会发生改变
  • 第三行:同第一行
  • 第四行:同第二行

还有一种可能:

a = (1,2,[3,4])
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

print(a,b,c,d,id(a),id(b),id(c),id(d))
print(a,b,c,d,id(a[2]),id(b[2]),id(c[2]),id(d[2]))

a[2][0] = 5

print(a,b,c,d,id(a),id(b),id(c),id(d))
print(a,b,c,d,id(a[2]),id(b[2]),id(c[2]),id(d[2]))

结果为:

(1, 2, [3, 4]) (1, 2, [3, 4]) (1, 2, [3, 4]) (1, 2, [3, 4]) 1783431521840 1783431521840 1783431521840 1783431577816
(1, 2, [3, 4]) (1, 2, [3, 4]) (1, 2, [3, 4]) (1, 2, [3, 4]) 1783402931464 1783402931464 1783402931464 1783402931848
(1, 2, [5, 4]) (1, 2, [5, 4]) (1, 2, [5, 4]) (1, 2, [3, 4]) 1783431521840 1783431521840 1783431521840 1783431577816
(1, 2, [5, 4]) (1, 2, [5, 4]) (1, 2, [5, 4]) (1, 2, [3, 4]) 1783402931464 1783402931464 1783402931464 1783402931848

这里我们一行一行看:

  • 第一行:因为外层是不可变类型,因此在浅拷贝时(只拷贝外层)会直接指向原有的内存地址,地址不会发生变化,而由于内层是可变类型,深拷贝(拷贝外层和内层)会发生变化
  • 第二行:但是内层由于是可变类型,而浅拷贝不拷贝内层,因此地址不变,深拷贝(拷贝外层和内层)会发生变化
  • 第三行:同第一行
  • 第四行:同第二行
值传递

在函数传参时,新的局部变量也支持创建了对原始对象的引用,而不是重新赋值。

def func(a):
    b = a
    print(id(a), id(b))

a = [1,2,3]
print(id(a))
func(a)

结果为:

1783402930824
1783402930824 1783402930824

而考虑到可变类型的性质,完全可以在函数中实现对传入的可变类型参数的修改和更新,该修改在函数外部是可见的。

类型检查

可以使用isinstance函数来检查某个对象是否是某个类型的实例,或者是某个类型列表中的实例:

Signature: isinstance(obj, class_or_tuple, /)
Docstring:
Return whether an object is an instance of a class or of a subclass thereof.

A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to
check against. This is equivalent to ``isinstance(x, A) or isinstance(x, B)
or ...`` etc.
Type:      builtin_function_or_method

 即下面的代码都是可以的:

isinstance([1,2,3], (list))
isinstance([1,2,3], (list, tuple))
属性访问

以下的内容是等效的:

getattr("hello","split")("l")
"hello".split("l")

即可以通过getattr函数来获取某个对象的属性和方法。

可迭代对象、迭代器、生成器

首先看一下《流畅的python》一书中对可迭代对象和迭代器的描述,以及本书中对生成器的描述:

可迭代对象:

  • 使用iter内置函数可以获取迭代器的对象
  • 如果对象实现了能够返回迭代器的__iter__方法,那么对象就是可迭代的
  • 序列都可以迭代
  • 实现了__getitem__方法,而且其参数是从0开始的索引,这种对象也可以迭代

迭代器:

  • 实现了无参数的__next__方法,返回序列中的下一个元素
  • 如果没有元素了,就抛出StopIteration异常
  • python中的迭代器还实现了__iter__方法,因此迭代器也可以迭代

生成器:

  • 生成器是构造新的可迭代对象的一种简单方式
  • 一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。
  • 要创建一个生成器,只需将函数中的return替换为yeild即可

根据上边的内容,如果要判断对象是否可迭代,可以通过iter函数来实现,如果可迭代,返回的就是迭代器,如果不可迭代就会报错:

def isiterable(obj):
    try:
        iter(obj)
        return True
    except TypeError: # not iterable
        return False

创建生成器则可以借助yield关键字:

def func(n = 10):
    for i in range(1, n + 1):
        yield i ** 2

print(func())
for it in func():
    print(it, end = " ")

 结果为:

<generator object func at 0x0000019F3CD0D7C8>
1 4 9 16 25 36 49 64 81 100 
is和==

简单说来is是判断两者是否指向同一内存,而==则是判断两者值是否相等:

a = [1,2,3]
b = [1,2,3]
print(a == b, a is b)

结果为:

True False
None

需要注意的是,None在python中是用单例实现的,即只存在一个None对象的实例。

a = None
b = None
print(a == b, a is b)

结果为:

True True
 序列函数

常用的会有以下几个:

  • enumerate:能够返回索引和值的元组序列
  • sorted:可以返回新的有序列表
  • zip:可以将多个列表、元组或其它序列成对组合成一个元组列表
  • reversed:可以从后往前迭代一个序列

比如enumerate:

for index, value in enumerate(range(3,0,-1)):
    print(index, value)

结果为:

0 3
1 2
2 1

sorted则会返回新的有序列表:

sorted([2,4,6,8,3])

结果为:

[2, 3, 4, 6, 8]

还有zip:

for item in zip(range(3), range(3)):
    print(item)

结果为: 

(0, 0)
(1, 1)
(2, 2)

但reversed返回的则是生成器:

print(reversed(range(3)), list(reversed(range(3))))

结果为:

<range_iterator object at 0x0000019F3CEAC250> [2, 1, 0]
推导式

列表推导式使用中括号实现:

tmp = [x ** 2 for x in range(10) if x % 2 == 0]
print(tmp, end = " ")

结果为:

[0, 4, 16, 36, 64] 

集合推导式用大括号实现:

tmp = {x ** 2 for x in range(10) if x % 2 == 0}
print(tmp, end = " ")

结果为:

{0, 64, 4, 36, 16} 

字典推导式也用大括号实现,不过和集合推导式格式有区别:

tmp = {x:x ** 2 for x in range(10) if x % 2 == 0}
print(tmp, end = " ")

结果为:

{0: 0, 2: 4, 4: 16, 6: 36, 8: 64} 

生成器推导式用小括号实现:

tmp = (x ** 2 for x in range(10) if x % 2 == 0)
print(tmp, list(tmp))

结果为:

<generator object <genexpr> at 0x0000019F3CD0DED0> [0, 4, 16, 36, 64]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值