使用 Python 将数据写入 Parquet 的 4 种方法:比较
原文:
towardsdatascience.com/4-ways-to-write-data-to-parquet-with-python-a-comparison-3c4f54ee5fec
学习如何高效地使用 Pandas、FastParquet、PyArrow 或 PySpark 将数据写入 Parquet 格式。
·发表于 Towards Data Science ·7 分钟阅读·2023 年 3 月 13 日
–
按需课程 | 推荐
一些读者联系我,询问关于 使用 Python 和 PySpark 进行数据工程 的按需课程。这些是我推荐的 3 个很好的资源:
还不是 Medium 会员?考虑通过我的 推荐链接 注册,以仅需每月 $5 即可访问 Medium 提供的所有内容!
介绍
在今天的数据驱动世界中,高效存储和处理大规模数据集是许多企业和组织的关键需求。这就是 Parquet 文件格式发挥作用的地方。
Parquet 是一种列式存储格式,旨在优化数据处理和查询性能,同时最小化存储空间。
它特别适合需要快速高效分析数据的用例,如数据仓库、大数据分析和机器学习应用。
在本文中,我将演示如何使用四个不同的库将数据写入 Parquet 文件:Pandas、FastParquet、PyArrow 和 PySpark。
特别是,你将学习如何:
-
从数据库中检索数据,将其转换为 DataFrame,并使用这些库中的每一个将记录写入 Parquet 文件。
-
以批量形式将数据写入 Parquet 文件,以优化性能和内存使用。
到本文结束时,你将对如何使用 Python 写入 Parquet 文件有一个全面的理解,并解锁这种高效存储格式的全部潜力。
数据集
作为本教程的一部分使用的数据集包括关于不同货币和不同公司每日账户余额的模拟数据。
数据是通过 Python 递归函数生成的,然后插入到 SnowFlake 数据库表中。
然后,使用 Python 的 snowflake.connector
或本地 PySpark 连接工具(配合 jars)建立与数据库的连接,以检索数据集并将其转换为 DF 格式。
实现上述步骤的代码可在 这里 获得,而 DF 的前几行如下所示:
作者生成的模拟数据,已获取并转换为 DF。
现在,让我们描述四种使用 Python 将数据集写入 Parquet 格式的不同策略。
探索 4 种写入 Parquet 文件的方法
如你所见,所有方法的共同起点是将数据已转换为 Pandas 或 PySpark DF。
这将使代码更加简洁、可读,并让你的生活更轻松。
方法 # 1:使用普通 Pandas
将数据集写入 Parquet 文件的最简单方法可能是使用 pandas
模块中的 to_parquet()
方法:
# METHOD 1 - USING PLAIN PANDAS
import pandas as pd
parquet_file = 'example_pd.parquet'
df.to_parquet(parquet_file, engine = 'pyarrow', compression = 'gzip')
logging.info('Parquet file named "%s" has been written to disk', parquet_file)
在这种情况下,使用了 engine = 'pyarrow'
,因为 PyArrow 是一个高性能的 Python 库,用于处理 Apache Arrow 数据。它提供了 Parquet 文件格式的快速和内存高效的实现,可以提高 Parquet 文件的写入性能。
此外,PyArrow 支持多种压缩算法,包括 gzip
、snappy
和 LZ4
。
在本教程中,让我们假装目标是实现高压缩比(即生成更小的 Parquet 文件),即使这意味着压缩和解压速度较慢。这就是为什么使用了 compression = 'gzip'
。
方法 # 2:使用 Pandas 和 FastParquet
另一种流行的将数据集写入parquet
文件的方法是使用fastparquet
包。在最简单的形式下,它的write()
方法接受一个pandas
数据框作为输入数据集,并可以使用多种算法进行压缩:
# METHOD 2.1 - USING PANDAS & FASTPARQUET (WRITE IN FULL)
import pandas as pd
import fastparquet as fp
parquet_file = 'example_pd_fp_1.parquet'
fp.write(parquet_file, df, compression = 'GZIP')
logging.info('Parquet file named "%s" has been written to disk', parquet_file)
但如果fastparquet
可以处理pandas
数据框,那么为什么不使用方法 #1呢?
那么,使用fastparquet
包至少有几个理由:
-
它被设计成一个轻量级包,以其快速的写入性能和高效的内存使用而闻名。
-
它提供了
parquet
格式的纯 Python 实现,并为开发人员提供了更多的灵活性和选项。
例如,通过使用fp.write()
方法,你可以指定选项append = True
,这是to_parquet()
方法尚不支持的。
当源数据集太大,无法一次写入内存时,这个选项特别方便,因此为了避免OOM
错误,你决定将其分批写入parquet
文件中。
下面的代码是一个有效的示例,展示了如何利用pandas
和fastparquet
的强大功能,将大型数据集分批写入parquet
文件:
# METHOD 2.2 - USING PANDAS & FASTPARQUET (WRITE IN BATCHES)
import pandas as pd
import fastparquet as fp
# SETTING BATCH SIZE
batch_size = 250
data = db_cursor_sf.fetchmany(batch_size)
columns = [desc[0] for desc in db_cursor_sf.description]
# CREATES INITIAL DF INCLUDING 1ST BATCH
df = pd.DataFrame(data, columns = columns)
df = df.astype(schema)
# WRITES TO PARQUET FILE
parquet_file = 'example_pd_fp_2.parquet'
fp.write(parquet_file, df, compression = 'GZIP')
total_rows = df.shape[0]
total_cols = df.shape[1]
# SEQUENTIALLY APPEND TO PARQUET FILE
while True:
data = db_cursor_sf.fetchmany(batch_size)
if not data:
break
df = pd.DataFrame(data, columns = columns)
df = df.astype(schema)
total_rows += df.shape[0]
# Write the rows to the Parquet file
fp.write(parquet_file, df, append=True, compression = 'GZIP')
logging.info('Full parquet file named "%s" has been written to disk \
with %s total rows', parquet_file, total_rows)
上述代码使用的策略是:
-
定义一个
batch_size
:在本教程中,它设置为仅250
行,但在生产中可以根据执行作业的工作机器的内存,轻松增加到数百万行。 -
从数据库中提取第一批行,使用
fetchmany()
而不是fetchall()
,并使用此数据集创建一个pandas
数据框。将此数据框写入parquet
文件。 -
实例化一个
WHILE loop
,它将不断从数据库中分批提取数据,将其转换为pandas
数据框,最终附加到初始的parquet
文件中。
WHILE loop
将持续运行,直到最后一行被写入parquet
文件。由于batch_size
可以根据使用情况进行更新,因此这应该被视为一种更加*“可控”*和内存高效的方法来用 Python 写入文件。
方法 # 3:使用 Pandas & PyArrow
在教程早期提到过,pyarrow
是一个高性能的 Python 库,也提供了parquet
格式的快速和内存高效实现。
它的强大功能可以通过间接使用(设置engine = 'pyarrow'
,如方法 #1所示)或直接使用其一些本地方法。
例如,你可以很容易地复制方法 # 2.2中写入parquet
文件的代码,使用ParquetWriter()
方法:
# EXAMPLE 3 - USING PANDAS & PYARROW
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
# SETTING BATCH SIZE
batch_size = 250
parquet_schema = pa.schema([('as_of_date', pa.timestamp('ns')),
('company_code', pa.string()),
('fc_balance', pa.float32()),
('fc_currency_code', pa.string()),
('gl_account_number', pa.string()),
('gl_account_name', pa.string())
])
parquet_file = 'example_pa.parquet'
total_rows = 0
logging.info('Writing to file %s in batches...', parquet_file)
with pq.ParquetWriter(parquet_file, parquet_schema, compression='gzip') as writer:
while True:
data = db_cursor_pg.fetchmany(batch_size)
if not data:
break
df = pd.DataFrame(data, columns=list(parquet_schema.names))
df = df.astype(schema)
table = pa.Table.from_pandas(df)
total_rows += table.num_rows
writer.write_table(table)
logging.info('Full parquet file named "%s" has been written to disk \
with %s total rows', parquet_file, total_rows)
请注意,由于pyarrow
在幕后利用了 Apache Arrow 格式,因此ParquetWriter
需要一个pyarrow
模式作为参数(这些数据类型相当直观,并且与其 pandas
对应类型有些类似)。
此外,在使用此包时,你不能直接写入 pandas
数据框,但需要先将其转换为 pyarrow.Table
(使用 from_pandas()
方法),然后可以使用 write_table()
方法将所需数据集写入文件。
尽管方法 #3相比其他方法更为详细,但如果在声明模式和列统计信息的可用性对你的用例至关重要,Apache Arrow 格式尤其推荐。
方法 #4:使用 PySpark
写入 parquet
文件的最后一种且可能是最灵活的方法是使用 pyspark
原生的 df.write.parquet()
方法。
当然,以下脚本假设你已连接到数据库并成功加载数据到数据框中,如 这里 所示。
注意,在这种情况下使用了 mode('overwrite')
,但你可以轻松切换到 mode('append')
,如果你希望批量写入数据。此外,pyspark
允许你指定大量选项,其中包括首选的 'compression'
算法:
# EXAMPLE 4 - USING PYSPARK
from pyspark.sql.types import *
from pyspark.sql import SparkSession
from pyspark import SparkConf
# CONNECT TO DB + LOAD DF
# WRITING TO PARQUET
df.write.mode('overwrite')\ # or append
.option('compression', 'gzip')\
.parquet("example_pyspark_final.parquet")
df.show()
结论
在这篇文章中,我们探索了四种不同的 Python 库,允许你将数据写入 Parquet 文件,包括 Pandas、FastParquet、PyArrow 和 PySpark。
实际上,Parquet 文件格式是需要快速高效处理和分析大型数据集的企业和组织的关键工具。
你还学习了如何批量写入数据,这可以进一步优化性能和内存使用。通过掌握这些技能,你将能够充分利用 Parquet 的强大功能,将数据处理和分析提升到一个新的水平。
来源
这 5 种 SQL 技术涵盖了 ~80% 的实际项目
原文:
towardsdatascience.com/5-advanced-sql-techniques-for-real-life-projects-f2db9b6680e2
加快你的 SQL 学习曲线
·发表于 Towards Data Science ·8 分钟阅读·2023 年 3 月 8 日
–
由 Possessed Photography 提供的照片,来源于 Unsplash
你是否对 SQL 感到好奇但又犹豫不决?或者你已经熟悉 SQL 的基础知识,但在将其应用于实际项目时遇到困难。
我了解这种感觉。
当我第一次开始学习 SQL 时,我被大量的信息所吓倒。即使今天,我仍然不断学习和探索新技术。
当然,SQL 的基础知识,如连接、子查询、过滤和排序,很容易掌握。但是,对于复杂的实际问题,你需要高级技术。
在这篇文章中,我想分享我在日常工作中最常用的五种高级 SQL 技术。通过掌握这些技术,你将能够完成几乎 80% 的生产级 SQL 查询,使你成为任何数据驱动项目的宝贵资产。
## Python To SQL — 我现在可以将数据加载速度提高 20 倍
上传大量数据的好、坏和丑陋的方法
towardsdatascience.com
我故意没有包括一些其他常用的技术,例如事务。如果你在分析角色中,这份方法列表将非常有用,而不是在软件工程师角色中。
在整篇文章中,我假设我们使用的是 Postgres 数据库。但如今每个主要的关系数据库都提供类似的功能。
1. 窗口函数
窗口函数是一种分析函数,它在与当前行相关的一组行中执行计算。窗口函数的结果与原始行一起返回,而不改变底层数据。
## 初级开发者编写多页 SQL 查询;高级开发者使用窗口函数
在记录的上下文中执行计算的一种优雅方法
[towardsdatascience.com
窗口函数的实际例子可能是计算特定产品随时间的销售收入累计总额。这对于识别销售趋势可能很有用,比如某些产品在一年中的某些时间最受欢迎。
这是一个如何使用 SUM
窗口函数计算特定产品随时间的销售收入累计总额的例子:
SELECT
product_id,
date,
SUM(revenue) OVER (PARTITION BY product_id ORDER BY date) AS running_total
FROM
sales
WHERE
product_id = 123;
在这个例子中,SUM
窗口函数用于计算特定 product_id
随时间的 revenue
累计总额。PARTITION BY
子句按 product_id
对数据进行分组,ORDER BY
子句按 date
对数据进行排序。running_total
列包含 SUM
窗口函数的结果。
为什么不使用 **group by**
?
当我第一次开始使用窗口函数时,这让我感到困惑。
是的,你可以在 PostgreSQL 中使用 GROUP BY
来聚合数据。然而,使用 GROUP BY
会提供与窗口函数不同的结果。
在计算特定产品随时间的销售收入累计总额的例子中,使用 GROUP BY
会按 product_id
和 date
对销售数据进行分组,然后计算每个组的收入总和。这将给你每一天的总收入,而不是随时间的累计收入。
这是一个使用 GROUP BY
按 product_id
和 date
聚合销售数据的例子:
SELECT
product_id,
date,
SUM(revenue) AS total_revenue
FROM
sales
GROUP BY
product_id,
date
WHERE
product_id = 123;
2. CTE: 公共表表达式
CTE 是一个可以在单个 SQL 语句中引用的临时命名结果集。它定义了一个可以在庞大的查询中多次引用的子查询,从而简化了复杂查询。
让我们通过一个例子来更好地理解这一点。假设你有一个包含所有客户订单的表。你想找到各个产品在表现最佳的地区的销售情况。
没有 CTE,你必须编写复杂的查询,涉及子查询、连接和聚合函数。这可能使查询难以阅读和理解。然而,使用 CTE 可以简化查询,使其更易读。
这是一个经典例子,用于理解 CTE 的有用性。
WITH regional_sales AS (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
), top_regions AS (
SELECT region
FROM regional_sales
WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT region,
product,
SUM(quantity) AS product_units,
SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;
在上面的查询中,我们使用了两个 CTE 来计算顶级销售区域中的畅销产品。
第一个 CTE,regional_sales
,通过对orders
表的amount
列求和并按region
分组,计算每个区域的总销售额。
第二个 CTE,top_regions
,仅选择那些总销售额超过所有区域总销售额 10%的区域。这是通过一个子查询来计算所有区域的总销售额并将其除以 10 实现的。
主查询然后使用IN
子句将orders
表与top_regions
CTE 连接,以仅包含来自顶级销售区域的订单。
这是没有使用 CTE 的重写查询:
SELECT region,
product,
SUM(quantity) AS product_units,
SUM(amount) AS product_sales
FROM orders
WHERE region IN (
SELECT region
FROM (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
) regional_sales
WHERE total_sales > (
SELECT SUM(total_sales)/10
FROM (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
) regional_sales_sum
)
)
GROUP BY region, product;
CTE 可以简化复杂的查询,使其更具可读性。它使在更大查询中多次重用相同的子查询变得更容易。
3. 递归查询
你是否曾经想从一个数据以层次结构或树状结构存储的数据库中检索数据?
例如,你可能有一个产品类别树,其中每个类别都有子类别,每个子类别还可以有进一步的子类别。在这种情况下,递归查询非常有用。
递归查询是指在定义中引用自身的查询。当遍历数据库中的树状或层次结构并检索所有相关数据时,它很有用。换句话说,它使你能够从一个依赖于同一表中数据的表中选择数据。
这是一个用于遍历类别树的递归查询示例:
WITH RECURSIVE category_tree(id, name, parent_id, depth, path) AS (
SELECT id, name, parent_id, 1, ARRAY[id]
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT categories.id, categories.name, categories.parent_id, category_tree.depth + 1, path || categories.id
FROM categories
JOIN category_tree ON categories.parent_id = category_tree.id
)
SELECT id, name, parent_id, depth, path
FROM category_tree;
我们在这个例子中使用了带有WITH
子句的 CTE 来定义递归查询。RECURSIVE
关键字告诉 Postgres 这是一个递归查询。
category_tree
CTE 由两个 SELECT 语句定义。第一个 SELECT 语句选择类别树的根节点(没有父节点的节点),第二个 SELECT 语句递归选择子节点。UNION ALL
操作符结合了两个 SELECT 语句的结果。
depth
列用于跟踪树中每个类别节点的深度。path
列是一个数组,用于存储从根节点到当前节点的路径。
使用这个查询,我们可以检索树中所有类别及其各自的深度和路径。
4. 动态 SQL
如果你曾经使用过 SQL 查询,你可能遇到过一些非常复杂的查询,需要在运行时生成。编写这些查询可能令人望而却步,而执行它们则可能更加具有挑战性。
过去,我曾依靠 Python 生成复杂的 SQL 查询,并使用像 psycopg2 这样的数据库连接器执行它们。这种方法有效但不太优雅。
然而,我最近发现了 Postgres 中的动态 SQL,这使得生成和执行复杂查询变得更加可控。使用动态 SQL,你可以根据运行时条件动态创建查询,这在处理复杂的数据结构或业务逻辑时非常有用。
假设你想检索在特定日期下的所有订单。在静态查询中,你可能会写成这样:
SELECT * FROM orders WHERE order_date = '2022-03-01';
但如果你允许用户选择一个日期范围呢?使用动态 SQL,你可以根据用户输入生成查询,如下所示:
EXEC SQL BEGIN DECLARE SECTION;
const char *stmt = "SELECT * FROM orders WHERE order_date BETWEEN ? AND ?";
DATE start_date, end_date;
EXEC SQL END DECLARE SECTION;
EXEC SQL PREPARE mystmt FROM :stmt;
EXEC SQL EXECUTE mystmt USING :start_date, :end_date;
在这个例子中,我们创建了一个函数,该函数接受两个参数,start_date
和 end_date
,并返回一个在该日期范围内的订单表。EXECUTE
语句允许我们根据输入参数动态生成查询,而 USING
子句则指定了查询的值。
这个例子很简单。但是在大规模项目中,你会需要大量动态生成的 SQL。
5. 游标
我们的查询可能会在受限环境中运行。一次性对一个庞大的表进行密集操作可能并不总是最佳选择。或者我们可能需要对操作有更多的控制,而不是将其应用于整个表。
这就是游标派上用场的地方。游标允许你逐行检索和操作结果集中的数据。你可以使用游标遍历数据集,并对每一行执行复杂的操作。
假设你有一个名为“products”的表,其中包含所有产品的信息,包括产品 ID、产品名称和当前库存。你可以使用游标遍历所有包含特定产品的订单,并更新其库存。
DECLARE
cur_orders CURSOR FOR
SELECT order_id, product_id, quantity
FROM order_details
WHERE product_id = 456;
product_inventory INTEGER;
BEGIN
OPEN cur_orders;
LOOP
FETCH cur_orders INTO order_id, product_id, quantity;
EXIT WHEN NOT FOUND;
SELECT inventory INTO product_inventory FROM products WHERE product_id = 456;
product_inventory := product_inventory - quantity;
UPDATE products SET inventory = product_inventory WHERE product_id = 456;
END LOOP;
CLOSE cur_orders;
-- do something after updating the inventory, such as logging the changes
END;
在这个例子中,我们首先声明了一个名为“cur_orders”的游标,选择所有包含特定产品 ID 的订单详细信息。然后我们定义了一个名为“product_inventory”的变量,用于存储该产品的当前库存。
在循环中,我们从游标中获取每个订单 ID、产品 ID 和数量,从当前库存中减去数量,并使用新的库存值更新产品表。
最后,我们关闭游标,并在更新库存后进行其他操作,例如记录更改。
结论
总之,SQL 是一种强大的语言,提供了许多处理复杂数据的技术。但是,初学时掌握所有这些技术可能会让你感到不知所措。
这篇博客文章探讨了五种最常用的高级 SQL 技术,包括 CTE、窗口函数、递归查询、动态查询和游标。虽然像联接和子查询这样的基本 SQL 概念对于处理数据是基础,但这些技术将帮助你处理几乎任何 SQL 项目。
虽然这篇文章提供了高级 SQL 技术的概述,但并不打算对每种技术进行详尽讨论。有关的链接已经提供,供那些希望深入探讨这些概念的人使用。未来,我计划对每种技术进行更深入的探讨,提供更全面的理解其能力和潜在应用。
感谢阅读,朋友!如果您喜欢我的文章,让我们在 LinkedIn、Twitter 和 Medium 保持联系。
还不是 Medium 会员?请使用这个链接成为会员,因为在没有额外费用的情况下,我会因推荐您而获得一小部分佣金。
5 个令人惊叹的 Python 隐藏功能 — 第一部分
原文:
towardsdatascience.com/5-awesome-python-hidden-features-a0172e0bd98e
PYTHON | 编程 | 特性
使用这些酷炫的隐藏 Python 功能,将你的编程技能提升到一个新水平
·发布于 Towards Data Science ·6 分钟阅读·2023 年 3 月 16 日
–
图片由 Emile Perron 提供,来源于 Unsplash
Python 是一种奇妙的编程语言。
Stack Overflow 的 2022 年开发者调查将 Python 排在 2022 年最受欢迎编程语言的第一位。
Python 非常适合初学者。它的语法简单易懂,大大减少了学习曲线的难度。
Python 是多才多艺的。得益于庞大而活跃的 Python 社区,Python 拥有强大的包和框架,能够解决几乎所有的开发需求。
想要编写 API 吗? 你可以使用 Python。
想要制作游戏吗? Python 能够满足你的需求。
想要处理数据并训练机器学习模型吗? 当然可以。Python 拥有适合你的工具!
Python 还隐藏着许多绝妙的技巧。我总是对所有能优雅解决复杂任务的 Python 一行代码感到惊讶!
在这篇文章中,我们将介绍 5 个酷炫的 Python 技巧,让你可以在同事面前炫耀 😜
隐藏功能 1:在 FOR 和 WHILE 循环中使用 ELSE
我们在开始编程时学习的第一件事之一就是条件语句(即if-else
块)。这些条件语句允许我们根据某些变量的值来改变代码的执行流程。在if
块中,我们检查某些逻辑。如果这个逻辑条件没有满足,我们就执行else
块中定义的代码。这些都是常识。
不过,我们也可以在任何for
或while
循环中使用else
关键字。在这种情况下,else
的功能是仅在循环成功完成且没有遇到任何break
语句时执行代码。
你可能会问,这有什么用处?
假设我们有一个数字列表。我们想要编写逻辑来确定元组中的任何单一数字是否为偶数。
通常,我们可能会写类似这样的代码:
# we define our numbers list
numbers: list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# we also define a flag variable to indicate if an even number was found
found_even: bool = False
for num in numbers:
# if number modulus 2 is 0, then it is even
if num % 2 == 0:
print(f"{num} is even")
# we set our flag to True because we found an even number
found_even = True
# we can stop execution because we found an even number
break
# if the flag is False, no even numbers where found
if not found_even:
print("No even numbers found")
这个逻辑相对简单。我们使用一个标志(在这个例子中是found_even
变量)来表示是否找到了偶数。如果在迭代过程中找到了偶数,我们使用break
关键字来停止循环执行。
上述代码也可以写成如下形式:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for num in numbers:
if num % 2 == 0:
print(f"{num} is even")
break
else:
print("No even numbers found")
我们现在不再需要标志变量found_even
。我们可以使用else
关键字来仅在循环迭代过程中从未达到break
关键字时打印“未找到偶数”。
隐藏功能 2:海象运算符
海象运算符(:=
)是在 Python 3.8 中引入的。我们使用海象运算符将变量与值分配为一个表达式。
参考以下示例。我们想要实现生成一系列随机数的逻辑,直到生成一个特定的数字。假设我们要得到我最喜欢的数字:10。我们通常会写类似这样的代码:
import random
rand = None
while True:
# generate a random number between 1 and 100
rand = random.randint(1, 100)
# if the random number is 10, break the execution
if rand != 10:
print(rand)
else:
break
# this will only be executed if we get a 10 and break the loop
print("We got a 10!")
在我们的循环中,我们生成一个随机数并将其存储在变量rand
中。迭代的次数是基于rand
变量的值。rand
变为 10 的时间越早,我们就越早打破循环。
现在,使用海象运算符,我们可以通过以下代码获得相同的功能:
import random
while (rand := random.randint(1, 100)) != 10:
print(rand)
print("We got a 10!")
在这里,我们告诉 Python 我们希望 while
循环在 rand
的值不等于 10 时运行。我们还告诉它,每次新迭代时,rand
将从 random.randint(1, 100)
获取其值。
隐藏功能 3:省略号
省略号(即...
)是一个有趣的关键字,在早期开发阶段非常有用。当处理复杂逻辑时,最佳策略是分而治之——将复杂的逻辑拆分成较小且更易于实现的部分。通常,这需要我们首先实现这些较小的函数,然后将所有内容结合在一起。
然而,我们有时(出于各种原因)想要定义函数但稍后再编写其代码。省略号允许我们做到这一点!
让我们看看如何实现。
def some_function(x, y, z):
# do something with x, y, z
...
# we can use it anywhere we like
some_list = [1, 2, 3, ...]
即使函数some_function
没有定义代码,上述代码也不会失败。我们甚至可以调用这个函数,它仍然不会失败(当然,它也不会返回任何东西)。
隐藏功能 4:函数属性
在 Python 中,任何函数都被存储为一个对象。任何对象都可以拥有属性。因此,在 Python 中,函数也可以有属性。
我们可以使用函数属性来定义有关函数的附加信息和其他元数据。例如,假设我们想跟踪某个特定函数被调用的次数。我们可以设置一个计数器属性,在每次调用后递增它。
def my_function(x):
return x * 2
my_function.counter = 0
my_function.counter += 1
print(my_function.counter)
函数属性的另一个有趣用例是设置 is_verbose
属性,以便在打印额外信息时进行切换。这通常通过向函数传递一个额外的参数来实现。使用函数属性,我们将不再需要额外的参数。
另一个好的例子是展示函数的文档字符串。
def my_function(x):
"""This is a docstring for my_function."""
return x * 2
print(my_function.__name__)
print(my_function.__doc__)
通过调用属性 __name__
,我们指示 Python 打印函数的名称。__doc__
则打印函数的文档字符串。
函数属性有许多用途。你可以在这里阅读更多关于它们的内容:
[## PEP 232 - 函数属性
这个 PEP 描述了对 Python 的扩展,为函数和方法添加属性字典。这个 PEP 跟踪了……
隐藏特性 5:三元运算符
Python 中的三元运算符是一种将 if-else
语句定义为单行代码的方法。
请考虑下面的示例:
x = 5
y = 10
if x > y:
result: str = "x is greater than y"
else:
result: str = "y is greater than or equal to x"
print(result)
我们可以使用三元运算符语法来获得相同的功能,如下所示:
x = 5
y = 10
result = "x is greater than y" if x > y else "y is greater than or equal to x"
print(result)
你可以在这里找到本系列的第二部分:
5 个更多令人惊叹的 Python 隐藏特性 — 第二部分
探索一些强大的功能以释放 Python 的全部潜力
towardsdatascience.com](/5-more-awesome-python-hidden-features-part-2-160a533c212b?source=post_page-----a0172e0bd98e--------------------------------)
结论
在这篇文章中,我们讨论了 5 个不被认为是常识的 Python 特性。这些隐藏特性的目的不仅仅是让我们炫耀 Python 技能。它们确实可以节省宝贵的开发时间,提高代码的可读性,并帮助我们编写更高效、更美观的代码。
你喜欢这篇文章吗?每月 5 美元,你可以成为会员,解锁对 Medium 的无限访问权限。你将直接支持我和 Medium 上所有你喜欢的作家。非常感谢!
## 使用我的推荐链接加入 Medium - David Farrugia
阅读 David Farrugia 的每一篇故事(以及 Medium 上成千上万的其他作家)。你的会员费用直接支持……
也许你还可以考虑订阅我的邮件列表,以便在我发布新内容时收到通知。这个服务是免费的 😃
## 获取 David Farrugia 发布内容的邮件通知
每当 David Farrugia 发布新内容时,你会收到一封邮件。通过注册,你将创建一个 Medium 帐户(如果你还没有的话)……
想要联系我吗?
我很想听听你对这个话题的看法,或者关于人工智能和数据的任何意见。
如果你希望联系我,可以发邮件到davidfarrugia53@gmail.com。
数据共享的 5 个好处
使数据民主化以释放其全部潜力
·
关注 发表在 Towards Data Science · 6 分钟阅读 · 2023 年 1 月 13 日
–
数据共享在许多组织中正成为常态,因为对从数据中提供价值的压力越来越大。事实上,2023 年是工作场所投资回报率之年, 这也包括数据。
无论你称之为数据网格、数据操作化、数据激活还是数据民主化,其核心理念是相同的:即向业务团队提供数据,帮助他们自主做出基于数据的决策。
数据共享的原则很简单:
-
每个人都应该能够访问他们所需的数据,而不仅仅是某些角色或职位。
-
应该没有障碍阻止人们获取他们所需的数据。
-
数据应以一种便于任何人访问、理解和使用的方式组织和结构化。
数据共享与自助服务的概念相一致,因为它消除了手动数据传递的需求。
有些人认为数据共享令人望而生畏。如果这导致混乱和混沌怎么办?好消息是,适当实施数据共享的好处通常会超过任何潜在的负面结果。
在这篇文章中,我将分享 5 个理由,说明为什么你应该开始与业务团队共享数据,以及你可以预期的积极影响。在即将到来的文章中,我将进一步探讨数据共享的正确实施方法。
数据共享可以为您的组织带来许多显著的好处,包括统一公司愿景、改善决策制定和提高生产力。此外,数据共享还可以帮助促进创新,并推动公司内部数据质量的提升。让我们深入了解一下!
数据共享的好处 — 图片由Castor提供
1 — 统一公司的愿景
数据共享有潜力通过创建一个员工可以自主访问和使用所需数据的市场,来使不同团队围绕公司战略达成一致。
在实践中,这意味着所有部门都可以访问相同的数据及其相关背景。也就是说,它让每个人在定义、指标计算方式和最佳数据集使用方面达成共识。这确保了每个人使用相同的语言,基于共享的定义和词汇。这会导致不同部门产生的一致报告。
在缺乏数据共享的情况下,各部门可能会孤立运作,它们的报告经常出现不一致的情况。其风险在于,各单位可能对相同概念使用不同的定义和计算方法。当人们有不同的定义时,他们的报告内容也会不同。相互矛盾的报告会减慢决策速度,并导致信任的侵蚀。数据共享使公司能够消除这种不一致。
此外,数据共享提供了对组织运作方式的更大透明度。每个部门可以跟踪进展,看到全局。这可以促进合作,帮助大家朝着相同的目标努力,而不是专注于各自孤立的观点。
2 - 提升决策能力
一旦你开放数据访问,通常会看到决策能力的提升。
当业务部门能够访问相关数据时,他们能够做出有根据和准确的决策。不再需要猜测或依赖直觉。
数据共享确保业务团队在日常操作中整合数据而不妨碍速度。这样,数据可以支持所有部门的实时决策,并通过报告促进长期战略决策。
此外,数据民主化消除了决策过程中的传统瓶颈。当只有技术团队能够访问数据时,其他团队必须寻求他们的帮助来获取信息。
在许多组织中,向各种用户分发数据仍然是手动进行的。首先,业务用户请求一个数据集。然后,分析团队接收请求,解读请求,并尝试与业务团队确认请求。当解释错误时,这种小范围的来回交流可能会持续一段时间。
这延迟了决策过程并加重了技术团队的负担。此外,需要跟上业务人员请求的数据团队扩展性差。
数据共享解放了分析团队,使其能够进行更深入、更有意义的数据分析。当分析可以脱离基本报告 — 向其他团队提供数据事实,和回答票务问题 — 时,他们可以专注于我们真正需要分析师技能的地方。
总的来说,数据共享改善了决策制定,同时释放了技术团队宝贵的时间。简而言之,这意味着每个人都能做出更好、更快、更聪明的决定。还有什么不喜欢的呢?
3 — 提高生产力
这还不止于此。数据共享还影响到所有团队的生产力。
当利益相关者能够访问单一真实来源时,他们可以更快地完成任务。
依赖手动过程来请求和交付数据时,速度是一个主要问题。通过数据共享,利益相关者不需要花时间寻找数据或等待他人提供数据。这也减少了错误的风险,因为员工可以访问最新和最准确的数据。
数据共享还消除了重复的工作和仪表板的重建。如果某些东西已经存在,利益相关者会知道。他们会在现有的基础上进行改进,而不是从头开始一个新项目。而且,重用比重建更高效。
当部门孤立运行时,不同团队会花时间重建相同的东西。这是因为他们没有检查某个材料是否已经存在的方式。
数据共享因此使组织能够利用其集体知识。这节省了时间和资源,使组织更高效、更具生产力。
4- 促进创新
数据共享通过增加跨部门协作和消除团队之间的壁垒来促进创新。
正如之前所述,数据共享使员工能够访问和使用他们可能无法自己获得的数据。这可以激发他们之前未曾考虑过的新想法和方法。
例如,如果市场部门的员工可以访问销售部门的数据,他们可能会发现新的方法来定位潜在客户或优化他们的营销策略。
其次,数据共享促进了团队之间的合作。它使个人可以看到其他人正在做什么,并分享他们自己的想法和见解。这可以带来新的视角和方法。这些通常不会在团队各自独立时出现。
因此,数据共享可以创造一个有利于创新的环境。它为员工提供了与同事合作和分享见解所需的信息。这将带来更好的产品、服务和流程。
5 — 改善数据质量
分享就是关心,特别是当涉及到数据时。事实上,数据共享可以帮助提高数据质量。数据共享增加了对数据准确性和完整性的审查和验证。
随着更多的人查看数据,更容易发现错误或数据质量差的地方。这带来了更大的数据质量责任,并能够修复可能出现的任何问题。
数据共享还增强了对数据可靠性的信任。当小组控制数据时,其他人可能很难验证其准确性或理解其收集的背景。
这可能导致对数据的怀疑和不信任,从而成为决策的障碍。因此,数据共享使每个人更容易理解和信任他们使用的数据。
因此,数据共享意味着更好的数据质量、增强的决策能力和改进的背景。这一切可以导致更智能的决策和不同部门之间更大的协调。这对我来说毫无疑问。
结论
数据共享将因两个原因继续获得关注:
-
公司希望充分利用其数据,因为从数据中获得投资回报的压力在增加。
-
越来越多的工具正在开发,以使数据对业务用户更容易访问。
数据共享有潜力改善战略对齐和增强决策过程。它还可以提高生产力、促进创新和改善数据质量。
然而,需要注意的是,如果实施不当,数据共享可能会导致混乱和混沌。我的下一篇文章将重点探讨如何以避免这些陷阱的方式建立数据共享,并帮助组织实现数据民主化的最终好处。敬请关注!
最初发布于 https://www.castordoc.com。
5 种最佳 Python 合成数据生成器及如何在数据不足时使用它们
获取更多数据
·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 1 月 23 日
–
图片来源 Maxim Berg
2021 年,每天产生了 2.5 万亿字节(2.5 百万 TB)的数据。今天的量更大。但显然,这还不够,因为 Python 生态系统中有许多库用于生成合成数据。有些可能是为了生成合成数据而创建的,但大多数都有有益的应用,例如:
-
机器学习:当真实数据不可用或难以获得用于模型训练时
-
数据隐私和安全:用真实但非实际的数据替换数据集中的敏感信息
-
测试和调试:在受控环境中使用合成数据测试和调试软件
-
数据增强:使用机器学习或统计方法从现有数据中人工生成更多数据点
本文将展示六个用于上述目的的 Python 库及其使用方法。
使用 Faker 生成随机用户信息
Faker 是最早的 Python 库之一,用于生成各种随机信息。一些常用的 Faker 生成的属性包括:
-
个人信息:姓名、生日、电子邮件、密码、地址
-
各种日期和时区信息
-
财务细节:信用卡、社会安全号码、银行信息
-
杂项:URLs、句子、语言代码
诸如此类。
它也有一个直观的 API。在初始化一个Faker
类后,你可以通过调用它的方法来生成一个新的虚假项目:
from faker import Faker
fake = Faker()
>>> fake.name()
'Nicole Perkins'
>>> fake.address()
'11669 Foster Cliffs Suite 161\nPort Elizabethfurt, OK 47591'
>>> fake.url()
'http://www.wade.com/'
所有这些方法在每次调用时都会返回新的项目,因此使用如下的代码片段构建一个人工 CSV 数据集非常简单:
import pandas as pd
df = pd.DataFrame(
[
{
"name": fake.name(),
"address": fake.address(),
"birthday": fake.date_of_birth(),
"email": fake.email(),
"password": fake.password(),
}
for _ in range(1000)
]
)
df.to_csv("data/fake.csv", index=False)
df.sample(5)
作者提供的图片
如果你注意到,姓名和电子邮件地址不匹配。这是使用 Faker 的一个缺点——Faker 生成的数据集在公开使用时很容易被识别。
了解更多内容,请访问 文档。
Sklearn 用于机器学习任务的合成数据集
Sklearn 是一个如此广泛且出色的库,它专门支持合成数据生成。
其 datasets
模块包含了许多生成各种机器学习任务的人工数据集的函数。最受欢迎的函数是 make_classification
和 make_regression
。
两者都有 n_samples
和 n_features
参数来控制生成的合成数据集的行数和特征数。
from sklearn.datasets import make_classification, make_regression
X, y = make_classification(
n_samples=5000, n_features=20, n_informative=15, n_classes=3, n_clusters_per_class=3
)
X, y = make_regression(n_samples=5000, n_features=20, n_informative=10)
为了控制任务的难度,你可以指定多少特征是有用的或冗余的,使用 n_informative
(相关)或 n_redundant
(信息特征的线性组合)参数。
make_classification
还提供了对分类目标的多种控制,即类别数、每个类别的簇数以及类别权重。
下面还有 make_blobs
函数用于生成聚类任务:
import seaborn as sns
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=500, n_features=2)
sns.scatterplot(X[:, 0], X[:, 1], hue=y);
作者提供的图像
如果你在寻找一些花哨的功能,还有其他函数,如 make_checkerboard、make_circles、make_moons 和 make_s_curve。
PyOD 中的离群值数据集
异常检测是数据科学中的一个普遍问题。但如果你想进行练习,质量高的带有离群值的数据集很难获得。幸运的是,Python Outlier Detection (PyOD) 库提供了一个生成带离群值的合成数据的实用函数:
from pyod.utils.data import generate_data
import seaborn as sns
import matplotlib.pyplot as plt
X, y = generate_data(
n_train=500, contamination=0.13, n_features=2, train_only=True, random_state=1
)
# Plot
sns.scatterplot(X[:, 0], X[:, 1], hue=y)
# Modify the damn legend
legend = plt.legend(labels=['Inlier', 'Outlier'])
legend.legendHandles[1].set_color("orange")
作者提供的图像
generate_data
提供了对训练集和测试集行数的控制,并且可以调整结果集中离群值的百分比(contamination
)。
PyOD 还拥有 Python 生态系统中最大的异常检测算法套件。要了解更多信息,你可以查看 我的异常检测课程。
使用 CTGAN 在另一个数据集之上生成合成数据
现在,进入精彩内容。
当数据有限时,机器学习模型很难很好地泛化而不会过拟合。在这种情况下,你可以使用条件生成对抗网络——CTGAN。
在你将其拟合到任何数据集后,CTGAN 可以从数据集的信息空间中生成高度匿名的合成样本。这是一种有效增加数据安全性和数据集规模的方法。
CTGAN由合成数据库 (SDV) 项目提供。它的 Python API 公开了一个CTGAN
类,该类需要学习的数据集和其分类列的列表。
然后,你可以使用sample
函数从中抽取任意数量的样本。下面,我们从陈词滥调的鸢尾花数据集中抽取了 20k 个合成样本:
import seaborn as sns
import pandas as pd
from ctgan import CTGAN
# Extract categorical data types
iris = sns.load_dataset("iris")
categoricals = iris.select_dtypes(exclude="number").columns.tolist()
# Fit CTGAN
ctgan = CTGAN(epochs=10)
ctgan.fit(iris, categoricals)
# Generate the data
synthetic_iris = ctgan.sample(20000)
synthetic_iris.head()
作者提供的图像。 鸢尾花数据集 (CC By 4.0).
Mimesis — 高级伪造工具
Mimesis 是一个建立在 Faker 基础上的完整随机信息生成器。它可以生成比 Faker 更多的随机属性:
from mimesis import Generic
from mimesis.locales import Locale
# Spanish locale
fake = Generic(Locale.ES)
print(dir(fake))
address code development food locale payment text
binaryfile cryptographic file hardware numeric person transport
choice datetime finance internet path science
它的随机生成器被分为 20 个类别,这使得 Mimesis 更加有条理。
它还大力支持 32 个地区(语言)的国家特定信息。下面,我们生成了一千行虚假的西班牙数据:
from mimesis import Generic
from mimesis.locales import Locale
import pandas as pd
# Spanish locale
fake = Generic(Locale.ES)
df = pd.DataFrame(
[
{
"name": fake.person.full_name(),
"country": fake.address.country(),
"birthday": fake.datetime.date(),
"email": fake.person.email(),
"password": fake.person.password(),
}
for _ in range(1000)
]
)
df.head()
作者提供的图像
你还可以创建自定义地区,其中你可以结合多种语言以获取区域特定的信息,即特定于西欧的数据。
从其大量文档中了解更多信息。
使用 TensorFlow 进行图像增强
在计算机视觉问题中,人工增加图像数据集规模的最有效方法之一是数据增强。
这个方法很简单:当你拥有一个小的图像数据集,数据量太小以至于神经网络无法有效训练时,你可以通过使用各种随机图像转换来增加图像数量。这样,网络将有更多多样的例子进行训练。常见的图像转换有:
- 几何变换:旋转、平移、缩放、翻转——改变图像中物体的大小、方向和位置
图像来自TensorFlow 文档 (Apache License)
- 颜色和亮度:随机改变亮度和对比度,以引入更多的光照和颜色条件变化
图像来自TensorFlow 文档 (Apache License)
- 噪声和模糊:添加随机噪声和模糊效果以模拟不同水平的图像质量
这种转换可以通过引入相似但不完全相同的图像变体显著增加数据集的大小。这反过来会提升神经网络的性能。
在 TensorFlow 中,可以通过多种方式执行图像增强。对于图像分类任务,有一个ImageDataGenerator
类:
import tensorflow as tf
train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
rescale=1.0 / 255,
rotation_range=10,
width_shift_range=0.1,
height_shift_range=0.1,
zoom_range=0.15,
horizontal_flip=True,
fill_mode="nearest",
)
你初始化它并设置你想要的转换。然后,你可以使用其flow_from_directory
方法从指定的数据目录中批量读取图像:
train_generator = train_datagen.flow_from_directory(
"data/raw/train",
target_size=(50, 50),
batch_size=32,
class_mode="categorical",
)
之后,你可以将train_generator
传递给 Keras 模型的fit
。生成器异步工作——当模型在处理一批数据时,生成器在后台应用变换并调整下一批图像的大小。
为了使flow_from_directory
正常工作,数据集文件夹的结构应具有如下层次:
$ tree -L 3 data/raw/train
data/raw/
├── train
│ ├── 0
│ ├── 1
│ ├── 2
| ...
├── validation
│ ├── 0
│ ├── 1
│ ├── 2
| ...
数据集必须包含训练和验证(以及测试)目录,且图像应按类别名称分组到不同的文件夹中。
如果你不能将数据集强制成这样的结构,还有其他替代方案。例如,当你使用 Keras Sequential API 构建模型时,你可以使用转换层:
from tensorflow.keras import layers
resize_and_rescale = tf.keras.Sequential([
layers.Resizing(IMG_SIZE, IMG_SIZE),
layers.Rescaling(1./255)
])
image_augmentation = tf.keras.Sequential([
layers.RandomFlip("horizontal_and_vertical"),
layers.RandomRotation(0.2),
])
model = tf.keras.Sequential([
# Add the preprocessing layers you created earlier.
resize_and_rescale,
data_augmentation,
layers.Conv2D(16, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
# Rest of your model.
])
结论
尽管全球已有如此多的数据,合成数据正变得越来越受欢迎。这一点在新兴的合成数据初创公司数量中有所体现。根据这份市场分析,2021 年全球合成数据生成行业的价值超过了 1 亿,并预计以 34.8%的年增长率增长。
在这篇文章中,我们只是浅尝辄止地了解了一些最受欢迎的开源替代方案。除非你在寻找企业解决方案,否则这些库足以满足你的基本需求。
感谢阅读!
喜欢这篇文章以及它那奇特的写作风格吗?想象一下能够访问到更多类似的文章,都是由一个才华横溢、迷人风趣的作者(顺便说一下,就是我 😃)所写。
只需 4.99 美元的会员费,你将不仅能访问我的故事,还能获取来自 Medium 上最优秀和最聪明的思想者的知识宝库。如果你使用我的推荐链接,你将赢得我的超级感激和一个虚拟的击掌来支持我的工作。
[## 使用我的推荐链接加入 Medium — Bex T。
获取对我所有⚡高级⚡内容的独享访问权限,在 Medium 上畅游无阻。通过请我喝一杯来支持我的工作…
ibexorigin.medium.com](https://ibexorigin.medium.com/membership?source=post_page-----f62bcf62d43c--------------------------------)
5 个阻碍机器学习应用的挑战
克服障碍,释放机器学习在商业中的全部潜力
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 3 月 27 日
–
图片由 Lukas Tennie 提供,来源于 Unsplash
根据 CB Insight 发布的人工智能报告,基于人工智能的初创企业在 2022 年第一季度获得了151 亿美元的融资。尽管这个数字对许多人来说可能看起来很有希望,但融资额已经回落到疫情前的水平。尽管投入了数十亿美元在人工智能领域,许多企业仍然在机器学习的采用上挣扎。原因有很多,这正是我们在本文中要探讨的内容。
如果你对了解一些阻碍企业实施机器学习系统的常见挑战感兴趣,那么这篇文章就是为你准备的。在这篇文章中,你将了解到五个阻碍企业采用机器学习的挑战。
以下是阻碍企业采用机器学习的五大挑战以及如何克服这些挑战的方法。
1. 改变对立的文化
如果你的企业文化发生对立的变化,实施机器学习将变得困难。对立可能来自各种不同的来源。可能来自不重视的高层管理人员,也可能来自不愿改变的员工,因为他们习惯于现有的系统。你如何处理这种对立将决定你的机器学习实施是失败还是成功。
如果对立来自高层管理人员,可能是因为他们不愿意冒险和投资于机器学习项目。另一方面,如果对立来自员工,可能是因为他们害怕失业。员工通常将自动化以及机器学习和人工智能视为敌人,而不是朋友。
一旦你识别出来源,就该采取纠正措施以改变他们的思维方式。例如,如果对立来自员工方面,你应该专注于说服他们,说明机器学习将节省他们的时间并使他们的工作更轻松。它可以自动化他们厌恶的繁琐和重复的任务。
另一方面,如果对立来自高层管理人员,你应该让他们看到投资的回报。一旦他们看到你的机器学习项目能够快速带来指数级的回报,他们将开始支持你的机器学习采用计划,而不是反对它。
不要做出空洞的承诺和保证,而是从小处开始,逐步推进。这不仅会给你前进的信心,还会向利益相关者展示机器学习的采用确实可以给他们的业务带来好处。首先关注快速获胜的案例,瞄准容易实现的目标,然后再将机器学习项目扩展到组织的其他部分。
2. 识别你的机器学习用例
假设你已经跨过了第一个障碍,并且没有变革阻力。下一个障碍是为你的业务找到合适的机器学习用例。毕竟,你不能仅仅因为其他人都在做机器学习就实施它。
在实施机器学习之前,你需要确定具体的机器学习用例。机器学习是一个广泛的领域,你应该对需要专注的子领域有清晰的认识。问问自己,你的机器学习计划是否利用了计算机视觉、自然语言处理或机器学习的机器人流程自动化能力。
回答这个问题将使你的业务能够制定清晰的策略。从最关键的业务功能开始,因为它可以帮助你证明你的观点。它可以是计算机视觉驱动的组装线或数据分析驱动的零售营销活动。
这可能因业务而异。你还可以将所有注意力转向解决根本性问题。这可能是流程导向的问题或其他任何问题。机器学习可以帮助你填补这些漏洞,从而提高业务效率和生产力。
3. 选择合适的训练数据
如果你的业务已经克服了变革阻力和用例问题?下一个障碍可能是找到合适的数据来训练你的机器学习算法。机器学习算法的效果取决于你用于训练它们的数据。强烈建议你用多个数据样本训练你的机器学习模型,这样它可以涵盖所有方面。
这意味着机器学习算法的结果受你用于训练的数据的影响很大。这就是为什么使用高质量、经过标准化且无任何偏见的数据对于获得机器学习模型的最佳结果至关重要。
即使你提供给这些机器学习模型的数据质量很高,它仍然需要进行标准化。跨多个数据源的数据一致性至关重要,因为任何数据的不一致性都可能对最终输出产生负面影响。
假设你训练机器学习模型的数据质量很高但数量较少,你仍然不会获得理想的结果。机器学习模型需要大量的数据来训练。你能提供给机器学习模型的高质量数据越多,最终输出效果就会越好。数据量大的另一个好处是它可以防止机器学习模型出现过拟合现象。过拟合是指机器学习模型对训练数据掌握得过于深入,以至于无法对新数据进行泛化。
总而言之,你输入机器学习模型的数据必须是多样化、全面、规范化且高质量的。数据的量也应更高,以便能够为现实世界问题提供正确的解决方案。
4. 将机器学习与人力资源融合
即使你已经正确训练了你的机器学习算法,仍然存在产生奇怪结果的风险。机器学习模型复杂,有时可能产生意外或甚至违背直觉的结果。例如,一个预测模型可能表明某个候选人最适合某个职位,但人力资源招聘人员可能会基于数据中未捕捉的其他因素不同意这一点。
当你完全依赖机器学习模型做出决策时,决策可能会存在某种偏见。由于机器学习利用历史数据来做决策,而这些数据可能包含偏见,因此也可能使你的决策产生偏见。最糟糕的是,这些机器学习模型可能会放大这些偏见,并将其融入最终输出中。
我们可以很容易地在自动简历筛选软件中看到这些例子,这些软件用于识别适合工作的候选人。哈佛商学院发现,这种招聘软件由于严格的选择标准而拒绝了许多候选人。由于机器学习模型有时会产生意外结果,持续的人类监督是必要的。
为了减少意外结果的可能性,必须在人类决策过程中保持参与,而不是完全交给机器。你可以通过包括人类专家审查或允许人类解读机器的最终结果来实现这一点。
5. 基础设施不足
机器学习的实施是一个资源密集型的过程,无论是从财务还是人力资源角度来看。你不仅需要具有实际操作经验的专业人员,还需要开发一个可能非常资源密集的机器学习基础设施。
不仅如此,企业还必须投资于能够运行机器学习算法和模型的硬件和软件。不论是强大的计算机系统、专业的软件工具还是高水平的存储基础设施,它们都是确保机器学习模型顺利运行的关键。
不仅如此,企业还必须投入时间、金钱和精力,开发一个由适当技能和专业知识支持的机器学习管道。没有这些,将无法有效运行机器学习算法,并从中获得最佳结果。
要开发一个机器学习基础设施,你需要具备模型选择、数据获取、数据可视化、模型测试等能力。此外,你还需要一个自动化的机器学习管道。这一切都需要大量投资,并不是每个企业都有足够的预算来资助这些活动。
结论
机器学习有可能彻底改变企业运营的方式,但目前有几个挑战阻碍了其普及。这些挑战包括数据质量不足、人才短缺、监管问题、可解释性和偏见。克服这些挑战需要多方面的方法,包括投资数据质量、提升员工技能、确保合规、使用透明且可解释的模型,并通过细致的算法设计来减轻偏见。通过解决这些挑战,企业可以释放机器学习的全部潜力,并在各自的行业中获得竞争优势。
在你看来,机器学习普及中的最大障碍是什么?在下面的评论区与我们分享。
每个数据科学家都应该知道的 5 种变点检测算法
时间序列分析中变点检测算法的基本指南
·发表于 Towards Data Science ·3 分钟阅读·2023 年 3 月 7 日
–
图片由 Gerd Altmann 提供,来自 Pixabay
时间序列分析是数据科学家必须了解的主题之一。时间序列分析包括用于查看时间序列数据的过程和数学工具集,以了解发生了什么、何时发生以及为什么发生,并预测未来最有可能发生的情况。
变点是时间序列数据中的突然变化,可能表示状态之间的过渡。在处理时间序列预测用例时,检测变点对于识别随机过程或时间序列的概率分布何时发生变化至关重要。
在样本时间序列图中可能的变点(已突出显示)
本文将讨论和实现 4 种变点检测技术,并对它们的性能进行基准测试。
1. 分段线性回归:
当变点发生时,时间序列数据的模式或趋势会发生变化。分段线性回归模型的基本思想是识别不同数据区域内的模式或趋势变化。在存在变点的情况下,系数的值通常会比邻近区域的值高或低。
**Pseudo-code of the Implementation:** 1\. Divide the time-series data into sub-sections of x (say 100) days
2\. Iterate through each sub-section of the data:
- Train data: enumerate of the data
- Target data: raw time-series value
- Train a linear regression model on train and target data
- compute coeffcient of the trained LR model
3\. Plot the coefficients
(作者提供的图片),线性分段变点检测算法的结果
上述图像中的红线代表每个线性回归模型在该时间序列数据子集或部分上的系数值。系数是乘以预测值的值,因此预测值越高,系数也越高,反之亦然。
(作者提供的代码),分段线性回归换点检测算法的实现
2. Change Finder:
Change finder 是一个开源的 Python 包,提供实时或在线换点检测算法。它使用 SDAR(Sequentially Discounting AutoRegressive)学习算法,期望换点前后的 AR 过程会有所不同。
SDAR 方法有两个学习阶段:
-
第一学习阶段:生成一个称为异常分数的中间分数
-
第二学习阶段:生成能够检测换点的换点分数
(作者提供的图像),change finder 换点检测算法的结果
(作者提供的代码),change finder 换点检测算法的实现
3. Ruptures:
Ruptures 是一个开源的 Python 库,提供离线换点检测算法。该包通过分析整个序列并分割非平稳信号来检测换点。
Ruptures 提供了 6 种算法或技术来检测时间序列数据中的换点:
-
动态规划
-
PELT (修剪精确线性时间)
-
核心换点检测
-
二分法分割
-
自下而上的分割
-
窗口滑动分割
(作者提供的图像),ruptures 换点检测算法的结果
(作者提供的代码),ruptures 换点检测算法的实现
结论:
在本文中,我们讨论了 3 种流行的实践技术来识别时间序列数据中的换点。换点检测算法有广泛的应用,包括医疗状况监测、人类活动分析、网站跟踪等。
除了上述讨论的换点检测算法,还有其他监督式和无监督式的 CPD 算法。
参考文献:
-
Change finder 文档:
pypi.org/project/changefinder/
-
Ruptures 文档:
centre-borelli.github.io/ruptures-docs/
感谢阅读
5 种代码优化技术,提高程序运行速度
原文:
towardsdatascience.com/5-code-optimization-techniques-to-speed-up-your-programs-cc7740381bcb
使用这些与语言无关的方法使你的代码更高效、更专业
·发布于 Towards Data Science ·7 分钟阅读·2023 年 11 月 29 日
–
图片来源于 Shubham Dhage 的 Unsplash
先使其工作,然后使其更快。这是许多专业程序员遵循的一个常见原则。起初,你可能会使用看起来最直观的方法编写代码,以节省草稿开发时间。在你获得一个可运行的实现后,你可能会希望通过仔细选择哪些技术数据结构在你的具体情况下效果最佳来优化代码。
在本文中,我们将探讨五种与语言无关的方法,你可以用来改善代码的运行时间。以下概念是通用的,可以应用于任何编程语言。
循环不变项提取
请考虑以下 Python 代码,它检查一个字符串列表与正则表达式的匹配情况:
import regex as re
# Get some strings as input
strings = get_strings()
# List to store all the matches
matches = []
# Iterate over the input strings
for string in strings:
# Compile the regex
rex = re.compile(r'[a-z]+')
# Check the string against the regex
matches = rex.findall(string)
# Finally, append the matches to the list
matches.extend(matches)
循环会将一组指令反复应用于变化的输入。考虑到这一点,你能在上面的代码中找到任何不变的操作吗?
语句 rex = re.compile(r’[a-z]+’)
在一个常量输入上操作:正则表达式字符串。在每次循环迭代中,这个语句都会做完全相同的事情,与循环的输入无关。如果我们将这个不变的语句提取出来,并在循环之前执行一次,代码仍然会保持相同的整体行为,同时节省一些 CPU 周期。
import regex as re
# Get some strings as input
strings = get_strings()
# List to store all the matches
matches = []
# Compile the regex only once before the loop
rex = re.compile(r'[a-z]+')
# Iterate over the input strings
for string in strings:
# Check the string against the regex
matches = rex.findall(string)
# Finally, append the matches to the list
matches.extend(matches)
一般来说,每个循环不变的变量或操作(不依赖于循环的输入或状态)都应该从循环中提取出来,只要代码逻辑保持不变。
有时,编译器会自动对你的代码应用这种优化。然而,它们并不总是能够检测到冗余语句,而且解释型语言没有提前优化的特权,因此你应该关注循环不变代码。
枚举状态和类型
在表示变量对象状态时,初学者程序员可能会想到使用字符串。考虑以下 Rust 代码:
struct Door {
pub state: &'static str,
}
impl Door {
fn new() -> Door {
Door { state: "closed" }
}
}
fn main() {
// Create a new door objetc
let mut door = Door::new();
// Set the door state to open
door.state = "open";
// Check if the door is open
if door.state == "open" {
println!("The door is open!");
}
// Set the door to another state
door.state = "semi-closed";
// Commit a typing mistake
if door.state == "semi-clsed" {
println!("This won't get printed!");
}
}
虽然字符串是一个直观的解决方案,但存在一些问题。首先,字符串状态容易出现输入错误,如最后一个 if 语句所示。此外,可能的状态有哪些?
不管怎样,我们来谈谈优化。字符串比较非常慢,因为你必须检查每一个字符以确定它们是否相等。此外,字符串需要比其他替代方案更多的字节来存储。例如,你可以使用枚举来表示对象状态,而无需担心输入错误,同时利用整数比较的速度。
struct Door {
pub state: DoorState
}
impl Door {
fn new() -> Door {
Door {
state: DoorState::Closed
}
}
}
enum DoorState {
Open,
Closed,
SemiClosed,
Locked,
}
fn main() {
// Create a new door object
let mut door = Door::new();
// Set the door state to open
door.state = DoorState::Open;
// Check the door state
if matches!(door.state, DoorState::Open) {
println!("The door is open!");
}
// Match all possible states
match door.state {
DoorState::Open => println!("The door is open!"),
DoorState::Closed => println!("The door is closed!"),
DoorState::SemiClosed => println!("The door is semi-closed!"),
DoorState::Locked => println!("The door is locked!"),
}
}
枚举是一种基于整数的抽象,所需存储的内存非常少。除此之外,枚举通常是按值传递的,从而避免了在比较等操作时的解引用开销。许多语言原生支持枚举,大多数语言允许这种模式。
代数和布尔操作
考虑以下包含条件语句的代码片段:
def is_zero(number):
if number == 0:
return True
else:
return False
def count():
counter = 0
MAX = 10
for i in range(100):
counter += 1
# Reset the counter when it reaches MAX
if counter == MAX:
counter = 0
如果语句被编译为条件跳转指令,与线性分支执行相比,这可能会显著降低你的代码速度。有时,可以用等效的表达式替代条件语句,通常更快,具体取决于其长度和操作。
以下是使用布尔和算术表达式优化过的前面的函数,而不是条件语句:
def is_zero_algebra(number):
# The result of comparison is already a boolean
return number == 0
def count_algebra():
counter = 0
MAX = 10
for i in range(100):
# Use the remainder operator to reset the counter
# when it reaches MAX
counter = (counter + 1) % MAX
然而,你应该始终对任何假定的优化替代方案进行基准测试,以检查它是否确实更快。
备忘录化
不,这不是拼写错误。备忘录化是一种算法优化技术,它包括记住函数的输出及其输入。当处理那些需要多次调用的资源密集型函数时,你可以将输入和结果存储在映射数据结构中,这样如果输入相同,就不需要重新计算函数。
一个可以通过备忘录化来改进的经典例子是计算斐波那契数列。考虑下面的代码,它计算斐波那契数列中的第 n 个数字:
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
R = 30
for i in range(R):
fibonacci(i)
对于较小的输入n
,函数的执行时间不会很长。然而,由于其时间复杂度为 O(2ⁿ),较大的输入值将导致显著更长的运行时间。
现在考虑另一种利用备忘录化的方式:
# Use a class to implement the memoization technique
class MemoizedFibonacci:
def __init__(self):
# When the object is created, initialize a map
self.memo = {}
def fibonacci(self, n):
if n <= 1:
return n
elif n not in self.memo:
# Store the input-output pair in a map data structure
self.memo[n] = self.fibonacci(n-1) + self.fibonacci(n-2)
# Return the stored output value that corresponds to the given input
return self.memo[n]
memoized_fibonacci = MemoizedFibonacci()
for i in range(R):
memoized_fibonacci.fibonacci(i)
从下图中可以看出,备忘录化极大地改善了函数的运行时间:
常规斐波那契函数和经过记忆化优化的替代函数的基准测试(数值越低越快)
这是因为这个记忆化函数的时间复杂度大致是线性的。
记忆化斐波那契函数的时间复杂度
使用特定案例的数据结构
数据结构选择的一个常见例子是普遍存在的链表与数组困境。你需要链表的 O(1)插入时间,还是需要数组的快速随机索引?在选择数据结构时,你必须比较每个选项的优缺点,以找到最适合你的情况的结构。有时,你甚至可能想要实现一个定制的数据结构,以完全符合你的要求。
下面是一些优化中常见的数据结构选择的其他示例:
-
写时复制实现(COW)。实现 COW 的结构允许你通过共享不可变引用高效且安全地传递数据。只有在你实际尝试修改数据时,它才会被复制,从而节省了不必要的操作时间。
-
循环缓冲区是实现队列行为或缓存数据时,常见的传统数组替代方案。
-
解析表是一种将查找表和哈希表组合在树状结构中的方式,这使你能够高效且简洁地解析结构化数据,而无需编写复杂且易出错的代码。它们在编译器和分类算法中被广泛使用。
主要要点
优化代码不是一项简单的工作。你需要首先找到相关的瓶颈。然后,仔细检查是否存在冗余操作或是否有更直接的方法来解决问题。你可能需要将你的想法草绘到纸上,以更好地可视化算法、内存布局和数据结构。最后,进行基准测试和测试,看看你是否真正改进了代码,或者破坏了某些功能。
我希望你喜欢这篇文章。如果你有任何想法,请在评论中分享。感谢阅读!
如果你有兴趣了解更多关于代码优化的信息,请查看以下关于查找表和哈希表的文章:
一种在专业代码库中普遍使用的代码性能实践。通过这些简单的示例学习如何使用它们。
分析师和数据科学家的 5 个常见数据治理痛点
理解支持创新的保护措施
·发表于Towards Data Science ·阅读时间 14 分钟·2023 年 8 月 24 日
–
图片:Headway(Unsplash)
你是大型组织的分析师还是数据科学家?
如果你曾遇到过这些让人挠头的问题,请举手:
-
寻找数据就像是进行一次福尔摩斯探险。
-
理解数据血统非常令人沮丧。
-
访问数据成为了与官僚主义怪兽的对决。
这是我常听到的一个普遍的调侃:
“那些数据治理的家伙真知道怎么让生活有趣……”
是时候对他们宽容一些了。
从我在澳大利亚一家大型银行担任工程师和数据科学家的经验来看,已经有半个十年,我有幸在这场激烈的争论中跨越两方:既是数据的饥渴消费者,又同时作为他人的守门员。
更新:我现在在YouTube上聊数据分析。
在这篇文章中,我将进行三部分的深入探讨……
-
基础知识:数据如何在组织中流动。这很混乱!
-
理解 数据用户常遇到的痛点。
-
启示:欣赏保护措施如何支持创新。
第三点非常重要。
全球组织都在争相成为数据驱动型公司。创新与适当控制之间的张力不断存在,以保障公司客户、员工和声誉的安全。
随着新的数据使用案例不断出现——这几乎是随时都在发生——数据治理结构尝试同步演进。而这通常是一个挑战,因为无拘无束的创新没有自然的速度限制。
无限量的数据自助餐听起来不错,直到你的公司因客户数据泄漏到暗网而被监管机构罚款数百万美元。
哎呀,应该有这些控制措施。
数据的生命之圈!
数据像下游的河流一样流经一个组织。
从捕获到使用,演变中的数据需要进行管理。
数据生命周期。图像由作者提供
这个数据生命周期的每个阶段都有其自身的…
-
利益相关者;
-
独特的风险;
-
商业和技术考虑;
-
道德困境;
-
监管要求…
…所有这些都需要被仔细管理。
让我们简要回顾一下每个阶段。我将从我的银行和金融行业中举一些例子。
1. 捕获
第一个阶段描述了数据在公司内部的诞生。
数据可以在操作源系统中创建,例如新的客户姓名或地址。它可以是前线银行人员在分行计算机中保存的客户 ID或员工 ID。或者可能是一个计算的指标,例如总利润或风险加权资产,这些数据汇集了其他维度表中的数据。它甚至可能是外部数据,例如中央银行的新现金利率,这将对我们的储蓄和抵押贷款产品产生连锁反应。
在捕获过程中,关键是定义我们正在捕获的内容,并定义对数据质量的期望。
如果一个数字代表某种货币,我们需要知道它是美元、英镑、人民币等。是否可以有空字段?是否总是需要有一定数量的数字,例如邮政编码(ZIP 码)?
这决定了我们需要具备的控制措施,以确保未来的数据符合期望。数据控制的目的是减少数据风险。
最后是隐私和合规性。如果数据涉及我们的客户和个人——这些数据可能是敏感的,可能会被用来识别他们——我们是否拥有捕获这些数据的同意?
如果处理不当,当发生重大黑客事件时,可能会摧毁业务。
2. 过程
现在我们正在处理这些数据。
它可能是在系统之间移动数据而不进行更改,例如将副本导入企业数据湖中供数据科学家稍后使用。(但领导者需要确保数据不会过时!)
或者应用业务规则来过滤、聚合或以其他方式转换数据——通常将其与其他数据源集成——以生成一个精细化的输出数据集,存储在企业数据仓库中,准备供业务使用。
企业数据格局的 30,000 英尺视图。来源:Z. Dehghani 于MartinFowler.com,作者进行了一些修订
在这个阶段,重要的是要考虑以下几点:
-
数据质量:在处理过程中没有引入错误;
-
可追溯性:跟踪数据来源,以便清楚地了解随时间的变化;
-
效率:战略性地设计转换以减少冗余的 ETL 管道,最小化技术债务;
-
问责制:为公司的不断变化的数据指定一个数据所有者。这至关重要,因为数据来源中的任何问责缺口都可能影响数据质量并导致可预防的风险。
猜猜看?大公司在所有这些方面都遇到困难。
错误会发生,找出错误的根源可能很麻烦——特别是当没有正确的流程和技术来有效跟踪数据来源时。
此外,大多数组织有一个不健康的习惯,让一堆数据管道累积,因为各个团队在孤岛中为每个新项目制作一个新的 ETL 管道。(这就是企业数据产品的作用所在。)
最终,分配数据所有权和让数据用户查找它们可能是一个长期存在的挑战——稍后会详细讨论。
近年来,像微软这样的公司推出了一体化分析平台。这些云解决方案提供了数据摄取、处理、分析和管理的无缝统一体验,从而使处理企业数据变得更简单。
3. 保留
在这里,我们关注的是数据存储。(但也包括备份和恢复!)
这些可能听起来无聊,但搞错的后果是灾难性的。一些关键考虑因素:
-
可用性。保存数据很容易。检索数据,尤其是批量或实时——大规模——则很困难。此外,数据是否对所有知识工作者无缝可发现和可访问?数据本身的价值很小;通过人力输入和分析从数据中提取的信息和见解才真正解锁价值。
-
架构。我们是将数据存储为适合 SQL 爱好者的写时模式数据仓库中的表格?数据应该如何建模?或者我们将数据存储为读时模式数据湖中的非结构化平面文件,这提供了更多的灵活性,但在处理较小数据集时性能较差?我们应该如何组织分区?实时处理和分析是否可能?如果平台出现故障,我们是否有足够的备份和恢复措施?
-
隐私与安全。我们的数据是否经过加密,并且在防止未经授权的访问和黑客攻击方面安全存储?尤其是我们那些受保护和敏感的数据,这些数据可能被用来识别我们的客户和人员。我们是否拥有正确的基于角色的访问权限(RBAC),以确保只有正确的人拥有正确的访问权限?在受监管的行业中,这些领域的泄露通常会导致巨额的数百万美元罚款、股价暴跌以及对公司声誉的重大打击。这不是开玩笑——我有过这样的经验。
4. 发布
啊,激动人心的部分。
这就是我们将数据转化为见解,并将其发布为信息或报告,以供内部和外部利益相关者消费的地方。
数据质量在这里至关重要,因为垃圾进垃圾出(GIGO)。
你是什么你吃的,报告和机器学习模型也是如此。
不良数据会导致不可靠的见解,这代表了任何组织中的主要数据风险来源。
由于确保公司所承担的庞大数据量的良好数据质量几乎是不可能的,大型公司通常会采用有针对性的策略,专注于其数据质量检查中的关键数据元素(CDEs)。
公司的关键数据元素(CDEs)构成了它们满足客户、投资者和监管要求所需的关键数据,并且这些数据需要最高级别的治理和审查。
目前,我的银行正在跟踪 1500 个 CDE,这些 CDE 在 1700 多个系统中流动。在之前的澳大利亚政府对整个银行业进行打击后,我们建立了一个由多个平台组成的‘操作系统’,该系统 24/7 监控这些 CDE 的数据质量,并在情况开始滑坡时自动创建事件。这是非常严肃的事务。
我所说的数据质量是什么意思?
以下是一些关键数据质量维度,按严重程度递减:
-
完整性:不完整的数据意味着我们可能缺少维持业务运转和满足合规要求的关键性信息。这就是你如何破产的。
-
有效性:虽然我们有记录,但它们可能并不有效——这意味着它们与我们在第 1 步中建立的定义模式不一致。这包括现实世界的限制(如负高度)和表特定规则(如唯一键)。
-
准确性:数据可以存在且有效,但完全错误。想象一下,你的银行不小心将 1000 万美元存入你的账户。(那会是一个令人愉快的惊喜吗?)
-
一致性:如果你处理的数据准确有效,但以多种方式表示,那么你会面临一致性问题。这是技术债务的一个来源,阻碍组织拥有单一的真实来源。一个经典的例子是地址在不同的源系统中被不同地书写。这会在下游产生多米诺效应,因为同一客户可能会在相同(但不同表示形式)的地址下出现多次。真糟糕。
另一个关键考虑因素是合规性和数据的伦理使用。
某个特定的数据分析项目是否通过了酒吧测试?它是否合法?我们是否需要获得同意才能将客户数据用于特定目的?
一个好的例子是一个市场分析项目的结果,可能涉及向客户发送有针对性的优惠。然而,他们可能没有给我们必要的同意来将我们的洞察用于营销目的。
大数据被用来理解客户偏好,比他们自己更了解。图片由作者提供
这些都是可能且常常使公司陷入困境的情况,导致重大财务和声誉损失。
5. 归档
最后一阶段是经常被忽视的数据归档或处理,即数据到达其生命周期的潜在终点时。
一般规则是应该清除不必要的数据。然而,某些数据出于监管目的必须保留,通常在澳大利亚的金融服务行业中需保留 7 年。
问题在于公司通常难以确保及时清除数据,而延长保留期限只会加大数据风险。
想象一下:发生数据泄露,20 年的客户数据被盗,尽管其中 13 年数据在使用期限到期后应已删除。这是一个常见的场景,完全可以通过良好的数据治理加以预防。
治理看门人与分析师的愿望
我会听你倾诉。
在讨论了数据生命周期以了解各个阶段面临的各种治理挑战后,让我们深入探讨分析师和数据科学家在追寻数据过程中提出的一些常见抱怨!
“为什么我找不到我想要的数据?我瞎了吗?!”
不!完全可以理解。大多数组织在建立必要的基础设施以应对不断扩展的数据海洋和在分析洞察方面的竞争压力时,都经历了艰难的过程。
随着数十年的技术债务以庞大的 ETL 管道和数据仓库的混乱形式出现,以及现代数据湖中倾倒的海量数据,找到数据已演变成一项艰巨的任务——即使是治理人员有时也不例外。
与全球公司一致,我的银行依赖于数据产品和数据民主化策略来应对这些生产力杀手。
“这些表格的数据拥有者是谁?这应该很简单吗?”
答案分为三个部分。这是具有挑战性的……
-
分配谁应该负责数据。数据在组织内流动并与其他来源整合时,谁应该拥有这些数据?我们公司的数据管理专家最近引入了一个“生产者、处理者和消费者”框架。这个方法将整个数据旅程——从捕获到消费——划分为不同的区域,确保在每个阶段都有一个人对数据负责。数据驱动型组织的基础始于端到端的数据责任。
-
说服人们他们应该负责数据。这是因为拥有数据意味着承担数据被不当使用的风险,加上协助管理数据的额外劳动。这要求很高,却没有多少回报。(数据拥有者通常不会因为这个角色得到额外的津贴。)因此,一些自然的数据拥有者不愿意承担这个角色。
-
维护数据拥有者的名单。同事们来来去去。人员换岗。而且他们没有太多的动力来卸任他们的数据拥有者角色。这导致已经负担过重的治理团队在试图跟踪谁是组织内成千上万数据集的当前拥有者。
这是一场挣扎。
“为什么治理相关方总是要求我做这么多文书工作才能访问数据?团队资源稀缺,我们必须花费大量精力在繁琐的程序上!”
因为急于从数据中挤压见解的分析师的目标与保持数据使用合规的需求并不自然对齐。
数据创新者在加速推进,而注重风险的人则踩着刹车。
以我的银行为例。任何尝试处理敏感客户数据的举动都触发了需要进行新的隐私影响评估。是的,这需要时间,也确实很烦人,但这是法律要求,追溯到欧洲发起并随后全球采用的通用数据保护条例 (GDPR)法律。
而且这里有个关键点:这份 PIA 文档作为防线,抵御着主要监管机构对我们开出的高达$50 百万的罚款,因为它展示了我们在推进之前已经考虑了数据使用案例的影响。因为每一次违规行为都是需要承担巨额罚款的。
保险听起来很贵……直到你需要它。
“为什么我的数据发现环境上有这么多条件?感觉像是被限制在狭小的空间里。我只想要一个良好的环境和我想要的所有数据。”
因为你在发现环境中做的所有事情都涉及风险。
数据可能会被泄露、不道德使用,甚至导致误导性的见解,损害公司。
这就是为什么数据治理团队会要求你在一开始就非常具体地说明你的数据和环境需求,以便他们能了解你的数据发现沙盒的风险概况。
这些游乐场应该根据用例(用例隔离原则)进行设置和组织,并在项目完成后立即退役。
听起来可能很苛刻,但数据发现环境因其…
-
范围蠕变,即项目团队试图在相同环境中挤入更多的用例——因此更多的分析师和数据。这意味着更多的人看到他们不该看到的数据,从而增加了数据风险。
-
寿命蠕变,即团队不断延长其环境的使用寿命,这增加了其工作支持业务正常运行(BAU)过程而缺乏适当控制和治理的可能性。这意味着,是的,你猜对了,更多的数据风险。
一切都是为了理解和最小化数据风险。
“为什么从数据发现环境中提取数据这么难?”
因为你可以做的事情,坦率地说,对于那些注重风险的人来说,是一场噩梦。
想象一下泄露、滥用,或数据影响决策,从而对数百万客户产生不利影响,或使我们陷入监管机构的困境。
这是一片雷区。
流行的一体化分析平台,在大型公司中受到严格管理。图片由作者提供
这就是为什么发现环境就像是受保护的沙箱,仅供实验使用。如果你希望你的见解达到最佳状态——即生产类似的过程,运行正常的业务——准备好接受一些严格的审查。
锁定数据是一种减少大量数据风险的全面控制措施。
最后的话
似乎我对上述痛点的回答只突显了数据治理控制与分析师和数据科学家所要求的迭代工作风格之间的脱节。
当你面临最后期限时,很容易把治理团队视为敌人。
但没有这些人员,炉子上的火最终会吞噬整个厨房。你的数据治理团队最终确保…
-
你的公司的数据资产是管理和组织的,因此在公司数据资产扩展时,你可以找到并访问你所需要的东西。(至少,这是目标。)
-
保护措施到位以确保数据的适当使用,并防止可能毁掉客户生活和年终奖金的悲剧性数据泄露。(是的,这是真的。)
更深入地反思,这种创新与监管之间的紧张关系存在于每一个努力中,无论大小。没有束缚的飞跃通常会跟随着一个恢复和反思的时期,为下一次跳跃奠定基础。
请耐心等待。
在全球宏观层面,我们看到像中国和美国这样的强国让他们的大型科技部门在几乎没有监督的情况下飞速发展,直到一代人后才收紧对企业家的监管。在个人层面,我们知道在剧烈身体运动后适当恢复的重要性,这是一个逻辑上的停顿点。
在企业数据环境中存在这种阴阳的动态并不令人惊讶。
随着全球数据量的指数级增长、数据使用案例的日益多样化以及计算能力的不断提升,企业必须定期休整、进行一些清理,确保数据堆栈得到适当的管理和控制。
在大型公司中,通常存在两个相互竞争的目标:
首先,利用数据进行进攻性策略,使公司能更有效地竞争。这种攻击角度通常由一位激进的首席数据官(CDO)、首席数字官(另一个 CDO)或更广泛的分析社区采取。
其次,以防御性的方式管理数据,重点关注合规性。目标是通过对齐流程和系统,解决业务绩效和监管问题,从前台和源系统到后台数据平台和报告工具,简化数据流动。
最顶尖的专家在这一精细平衡行为中表现得相当出色:给予他们的分析师和数据科学家探索和创新的自由,同时确保组织在数据使用方面保持合规和伦理,尊重客户数据。
你在数据治理方面有什么经验?
在 Twitter 和 YouTube 这里、这里 和 这里 找到我。
我的热门 AI、ML 和数据科学文章
-
人工智能与机器学习:快速入门 — 这里
-
机器学习与机械建模 — 这里
-
数据科学:现代数据科学家的新兴技能 — 这里
-
生成性 AI:大公司如何争先恐后地采纳 — 这里
-
ChatGPT & GPT-4: OpenAI 如何赢得自然语言理解之战 — 这里
-
GenAI 艺术:DALL-E、Midjourney 和 Stable Diffusion 解析 — 这里
-
超越 ChatGPT:寻找真正智能的机器 — 查看这里
-
现代企业数据战略解析 — 查看这里
-
从数据仓库与数据湖到数据网格 — 查看这里
-
从数据湖到数据网格:最新架构指南 — 查看这里
-
Azure Synapse Analytics 实战:7 个用例解析 — 查看这里
-
云计算入门:为您的业务利用云 — 查看这里
-
数据仓库与数据建模 — 快速入门课程 — 查看这里
-
数据产品:为分析构建坚实的基础 — 查看这里
-
数据民主化:5 种“数据普及”策略 — 查看这里
-
数据治理:分析师常见的 5 个痛点 — 查看这里
-
数据讲故事的力量 — 销售故事,而不是数据 — 查看这里
-
数据分析入门:谷歌方法 — 查看这里
-
Power BI — 从数据建模到惊艳报告 — 查看这里
-
回归分析:使用 Python 预测房价 — 查看这里
-
分类:使用 Python 预测员工离职 — 查看这里
-
Python Jupyter 笔记本与 Dataiku DSS — 查看这里
-
常见机器学习性能指标解析 — 查看这里
-
在 AWS 上构建 GenAI — 我的首次体验 — 查看这里
-
数学建模与 COVID-19 机器学习 — 查看这里
-
未来工作:在人工智能时代您的职业安全吗 — 查看这里
数据科学作品集的 5 个错误
原文:
towardsdatascience.com/5-data-science-portfolio-mistakes-52f6e0ebbe4a
如何制作一个能让你被聘用的作品集
·发布于Towards Data Science ·阅读时间 5 分钟·2023 年 5 月 19 日
–
图片由Kenny Eliason在Unsplash上拍摄
当尝试成为数据科学家时,不仅仅是你知道什么,还要展示什么。换句话说,有效地传达你的专业知识和过去的工作是整个过程中的一个关键部分。
最好的方法之一就是通过作品集网站来实现。作品集赋予你信誉,并使你的工作对客户和招聘经理随时可用。
然而,并非所有的作品集都是相同的。有一些关键的错误区分了差的作品集和好的作品集。在这篇文章中,我列出了 5 个错误,这些错误会确保你的作品集永远无法让你找到工作。
错误 1:不要制作作品集网站
最糟糕的作品集就是没有作品集。
404 消息用于作品集。图片由作者提供。
由于大多数人没有作品集,拥有任何东西都会带来大部分好处,并且让你在其他候选人中脱颖而出。
然而,如果你从未制作过网站,你可能会犹豫。你可能会担心这会花费金钱,或者需要花费数小时学习如何编写 HTML 和 CSS 代码。
我也经历过这样的情况。有趣的是,我花了 8 个月才制作好我的第一个作品集,这并不是因为有巨大的成本或时间投入,而是因为我拖延了自己。实际上,我的第一个作品集 没有花费我任何钱,而且大约花了一个周末时间来创建。
现在有这么多免费的工具,制作网站从未如此简单。例如,在过去的一篇文章中,我描述了使用 GitHub Pages 创建一个免费且简单的作品集(无需编写代码)的方法。
## 如何用 GitHub Pages 创建一个(免费的)数据科学作品集网站
朝着下一个数据科学角色迈进的 5 个简单步骤
错误 2:包括不相关的项目
当你试图成为所有人的一切时,你实际上适合任何人。
如果你避免了错误 1,下一步是挑选合适的项目来展示。但是什么样的项目算是“合适”的呢?
策划你的作品集时,一个至关重要的部分是了解你的受众。这个作品集是为谁准备的?他们在寻找什么?我怎样才能直接满足他们的需求?
例如,如果寻找入门级的数据科学角色,请挑选那些展现技术数据技能的项目(如 Python 和 SQL),而不要挑选更适合软件工程或网页开发的项目(如 javascript、HTML 等)。虽然后者在数据科学角色中可能有帮助,但它们并不是关键,最终会分散受众的注意力。
此外,选择一个细分领域可能会有所帮助。细分有助于让你在小池塘中成为大鱼,并帮助避免“我专注于一切”的推销。
即便如此,这可能使你的受众难以看到你如何融入他们的世界。如果受众不能迅速将你作品集中的内容与他们的问题联系起来,他们将会转向下一个候选人。
错误 3:尽可能地塞满 Kaggle 项目
1 个现实世界项目 > 10 个玩具项目。
选择项目时的一个常见错误是数量重于质量。这通常表现为作品集中充满了使用来自 Kaggle 等网站的玩具数据集的项目。
虽然这些项目可以是很好的教育工具,但它们缺乏现实世界数据科学项目的基本要素,例如理解商业问题、在现有流程中工作以及从多个来源综合数据。
在评估项目的质量时,关注影响力。使用影响力作为挑选项目的过滤器,不仅使你的工作更具印象力,还传达了你的工作对某人是相关的。影响力的例子包括:你赢得了黑客马拉松,工作被发表,节省了$X,X 人下载了该项目,你在 X 人面前展示了它,等等。
对于那些刚刚起步且缺乏好的项目选择的人来说,最明显的路径是做一个独立项目。虽然这说起来容易做起来难,但这里有一些快速提示来帮助指导你的努力。
-
根据你的兴趣或职位描述(理想情况下)量身定制项目
-
从现有的商业或组织中获取(现实世界的)数据
-
构建一个网络爬虫或探索公共 API 以聚合数据
-
贡献开源项目
-
寻找一个导师
错误 4:不要包含任何视觉内容
展示而不是讲述。
最糟糕的作品集是没有作品集。第二糟糕的是没有图片的作品集。
网站的关键优势在于你可以嵌入图片、gif 和视频,不仅可以描述你的工作,还可以展示它。与其通过文字来表达这一点,不如考虑下面的 gif。
左侧是项目的文字描述,而右侧则直接展示了演示。你觉得哪一个更具吸引力?
展示与讲述。(左)项目的文字描述。(右)视频演示。图片由作者提供。
如果你没有现成的漂亮图片,可以花时间制作它们。这里不需要任何花哨的东西。例如,在我的作品集中,我大多数图像都是使用 Keynote(即苹果的 PowerPoint 版本)制作的。
错误 5:忽视所有非技术方面
数据科学版的“全身都是肌肉,却没有腿”。
很容易被数据科学中所有酷炫且令人兴奋的技术方面所吸引,同时几乎忽略所有其他非技术方面。
过于技术化的数据科学作品集。图片由作者提供。
尽管技术技能至关重要,但非技术方面,如领域知识和沟通技能同样重要。如果招聘是由非技术人员进行的(这在没有专门数据科学团队的小型组织中很常见),这一点尤为重要。
一个简单的帮助方法是列出你曾经工作的所有领域。此外,拥有展示项目或技术主题的视频或文字内容可以帮助传达那些关键的沟通技能。
解决“需要经验才能找到工作,但需要工作才能获得经验”的循环
资源
社交:YouTube 🎥 | LinkedIn | Twitter
支持:请我喝咖啡 ☕️
免费获取我所写的每个新故事的访问权限。附言:我不会与任何人分享你的电子邮件。通过注册,你将创建一个…
5 种简单有效的 Python 日志使用方法
原文:
towardsdatascience.com/5-easy-and-effective-ways-to-use-python-logging-a9564bd17ccd
像专业人士一样使用 Python 日志
·发表于 Towards Data Science ·5 分钟阅读·2023 年 6 月 16 日
–
图像由作者生成
我敢打赌,几乎每个 Python 开发人员有时都会使用“print”进行调试。对于原型设计来说没有什么问题,但对于生产环境来说,有更有效的方法来处理日志。在这篇文章中,我将展示 Python “logging” 比“print”更灵活和强大的五个实际原因,以及为什么如果你之前没有开始使用它,你绝对应该使用它。
让我们开始吧。
代码
为了使事情更实际,我们考虑一个玩具示例。我创建了一个小应用程序,用于计算两个 Python 列表的线性回归:
import numpy as np
from sklearn.linear_model import LinearRegression
from typing import List, Optional
def do_regression(arr_x: List, arr_y: List) -> Optional[List]:
""" LinearRegression for X and Y lists """
try:
x_in = np.array(arr_x).reshape(-1, 1)
y_in = np.array(arr_y).reshape(-1, 1)
print(f"X: {x_in}")
print(f"y: {y_in}")
reg = LinearRegression().fit(x_in, y_in)
out = reg.predict(x_in)
print(f"Out: {out}")
print(f"Score: {reg.score(x_in, arr_y)}")
print(f"Coef: {reg.coef_}")
return out.reshape(-1).tolist()
except ValueError as err:
print(f"ValueError: {err}")
return None
if __name__ == "__main__":
print("App started")
ret = do_regression([1,2,3,4], [5,6,7,8])
print(f"LinearRegression result: {ret}")
这段代码可以运行,但我们能做得更好吗?显然可以。让我们看看在这段代码中使用“logging”而不是“print”的五个优势。
1. 日志级别
让我们稍微修改一下我们的代码:
import logging
def do_regression(arr_x: List, arr_y: List) -> Optional[List]:
"""LinearRegression for X and Y Python lists"""
try:
x_in = np.array(arr_x).reshape(-1, 1)
y_in = np.array(arr_y).reshape(-1, 1)
logging.debug(f"X: {x_in}")
...
except ValueError as err:
logging.error(f"ValueError: {err}")
return None
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
logging.info("App started")
ret = do_regression([1,2,3,4], [5,6,7,8])
logging.info(f"LinearRegression result: {ret}")
在这里,我将“print”调用替换为“logging”调用。我们做了一个小的更改,但它使输出变得更加灵活。通过使用“level”参数,我们现在可以设置不同的 日志级别。例如,如果我们使用“level=logging.DEBUG”,则所有输出都将可见。当我们确定我们的代码已准备好投入生产时,我们可以将级别更改为“logging.INFO”,调试消息将不再显示:
“INFO” 调试级别在左侧,“DEBUG” 在右侧,作者提供的图像
重要的是,除了日志的初始化之外,不需要任何代码更改!
顺便提一下,所有可用的常量可以在 logging/init.py 文件中找到:
ERROR = 40
WARNING = 30
INFO = 20
DEBUG = 10
NOTSET = 0
正如我们所看到的,“ERROR”级别是最高的;通过启用“ERROR”日志级别,我们可以抑制所有其他消息,只显示错误。
2. 格式化
从最后的截图可以看出,控制日志输出是很容易的。但我们可以做得更多来改进它。我们还可以通过提供“format”字符串来调整输出。例如,我可以指定如下格式:
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s] %(filename)s:%(lineno)d: %(message)s')
在没有其他代码更改的情况下,我将能够在输出中看到时间戳、文件名,甚至行号:
日志输出,作者提供的图片
大约有 20 个不同的参数,可以在手册的“LogRecord attributes”一节中找到。
3. 将日志保存到文件中
Python 的 logging 是一个非常灵活的模块,其功能可以很容易地扩展。假设我们想将所有日志保存到一个文件中以备将来分析。为此,我们只需要添加两行代码:
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s] %(message)s',
handlers=[logging.FileHandler("debug.log"),
logging.StreamHandler()])
如我们所见,我添加了一个新的参数“handlers”。一个 StreamHandler 正在控制台上显示日志,而 FileHandler,顾名思义,将相同的输出保存到文件中。
这个系统非常灵活。Python 中有许多不同的“处理器”对象,我鼓励读者自己去 查阅手册。正如我们已经知道的,日志几乎可以自动工作;无需进一步的代码更改。
4. 循环日志文件
将日志保存到文件中是一个不错的选择,但遗憾的是,磁盘空间不是无限的。我们可以通过使用循环日志文件轻松解决这个问题:
from logging.handlers import TimedRotatingFileHandler
...
if __name__ == "__main__":
file_handler = TimedRotatingFileHandler(
filename="debug.log",
when="midnight",
interval=1,
backupCount=3,
)
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s] %(message)s',
handlers=[file_handler, logging.StreamHandler()])
所有参数都是显而易见的。一个 TimedRotatingFileHandler 对象将创建一个日志文件,该文件会在每个午夜更换,并且只保存最近的三个日志文件。之前的文件将自动重命名为类似“debug.log.2023.03.03”的格式,经过 3 天的间隔后将被删除。
5. 通过套接字发送日志
Python 的 logging 出奇地灵活。如果我们不想将日志保存到本地文件中,我们可以只需添加一个套接字处理程序,它将使用特定的 IP 和端口将日志发送到另一个服务:
from logging.handlers import SocketHandler
logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] %(message)s',
handlers=[SocketHandler(host="127.0.0.1", port=15001),
logging.StreamHandler()])
就是这样;无需更多的代码更改!
我们还可以创建另一个应用程序来监听相同的端口:
import socket
import logging
import pickle
import struct
from logging import LogRecord
port = 15001
stream_handler = logging.StreamHandler()
def create_socket() -> socket.socket:
"""Create the socket"""
sock = socket.socket(socket.AF_INET)
sock.settimeout(30.0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return sock
def read_socket_data(conn_in: socket.socket):
"""Read data from socket"""
while True:
data = conn_in.recv(4) # Data: 4 bytes length + body
if len(data) > 0:
body_len = struct.unpack(">L", data)[0]
data = conn_in.recv(body_len)
record: LogRecord = logging.makeLogRecord(pickle.loads(data))
stream_handler.emit(record)
else:
logging.debug("Socket connection lost")
return
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] %(message)s',
handlers=[stream_handler])
sock = create_socket()
sock.bind(("127.0.0.1", port)) # Local connections only
sock.listen(1) # One client can be connected
logging.debug("Logs listening thread started")
while True:
try:
conn, _ = sock.accept()
logging.debug("Socket connection established")
read_socket_data(conn)
except socket.timeout:
logging.debug("Socket listening: no data")
这里的难点是使用 emit 方法,它将所有通过套接字接收到的远程数据添加到一个活动的 StreamHandler 中。
6. 奖励:日志过滤器
最后,对那些足够细心到这一部分的读者来说,还有一个小小的奖励。添加自定义日志过滤器也很简单。假设我们只想将 X 和 Y 的值记录到文件中以备将来分析。创建一个新的 Filter 类也很简单,它将仅保存包含“x:”或“y:”记录的日志:
from logging import LogRecord, Filter
class DataFilter(Filter):
"""Filter for logging messages"""
def filter(self, record: LogRecord) -> bool:
"""Save only filtered data"""
return "x:" in record.msg.lower() or "y:" in record.msg.lower()
然后我们可以轻松地将这个过滤器添加到文件日志中。我们的控制台输出将保持不变,但文件中只会有“x:”和“y:”的值。
file_handler = logging.FileHandler("debug.log")
file_handler.addFilter(DataFilter())
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s] %(message)s',
handlers=[file_handler, logging.StreamHandler()])
结论
在这篇简短的文章中,我们学习了几种将日志集成到 Python 应用程序中的简单方法。Python 中的日志系统是一个非常灵活的框架,绝对值得花时间深入了解它的工作原理。
感谢阅读,祝你未来的实验好运。
如果你喜欢这个故事,可以随时订阅Medium,这样你会在我发表新文章时收到通知,并且可以全面访问其他作者的数千篇故事。
5 个简单的 Python 特性,你可以立即开始使用以编写更好的代码
我使用 Python 已经超过 8 年了。以下是我喜欢的一些 Python 特性,它们能使你的代码焕然一新且高效。
·发布于 Towards Data Science ·阅读时间 7 分钟·2023 年 7 月 19 日
–
图片由 Chris Ried 提供,发布在 Unsplash
你必须承认,看到代码或拉取请求上出现类似“这真是超级干净 😎”或“没想到可以这样做”的评论,会让你感到非常愉悦。个人经验告诉我,拥抱良好的软件工程原则并充分利用现有的语言功能,是编写出别人会感激的好代码的秘诀。
作为一名 MLE,我每天都在使用 Python。由于其低门槛和庞大的科学工具生态系统,Python 是机器学习从业者的一个极佳选择。
这意味着,几乎没有软件工程知识的个人也可以快速开始使用 Python。
这最后一句话可以用两种不同的语气来表达:积极的或消极的(试试看!)。
起初这可能看起来是个福音,但从整体来看,缺乏软件工程原则(例如类型、对象)的约束使工程师(MLE)或科学家(DS/AS)不愿编写良好的代码(相信我,我们在软件工程师中已经有了不太好的声誉)。这不可避免地会导致大多数情况下的代码不可读、不可维护和无法测试。而更糟糕的是,有一天它会成为某个毫无防备的受害者最糟糕的噩梦,因为要重复使用这段恶劣的代码。你可能还会看到一种多米诺效应,即在糟糕代码之上构建的代码会导致……更多糟糕的代码。最终,这甚至可能会导致组织上的头痛。
总而言之,在 Python 中做事情很简单,但以正确的方式做事情却很困难。在与 Python 搅斗了 8 年之后,我仍在学习不同(和更好)的方式来改善我的代码。我很幸运有好的软件工程师会建设性地批评我的代码,当我以低效的方式做事时。如果你有同样的支持,那是你的幸运。在这里,我将分享一些可以提升你 Python 技能的方法。
1. 数据类有助于清除杂乱
比如你想管理一个学生及其身高的列表。你可以使用元组的列表来做到这一点。
students = [("Jack", 168), ("Zhou", 172), ("Emma", 165), ("Shan", 170)]
但如果你后来想添加其他属性,如体重、成绩和性别怎么办?如果不头痛并犯很多错误,你无法使用上述数据结构。你可以使用字典,但仍然显得笨重。更好的解决方案是使用 dataclasses
。
import dataclasses
@dataclasses.dataclass
class Student:
name: str
height: int
这样清爽多了!然后你只需实例化一堆 Student
对象。
students = [
Student(name=“Jack”, height=168),
Student(name=“Zhou”, height=172),
Student(name=“Emma”, height=165),
Student(name=“Shan”, height=170)
]
然后,你可以使用类似 students[0].name
的语法来访问属性。无需再依赖模糊的知识,例如名称在 0th
位置,或使用容易出错的字符串键(如果你使用字典的话)。你可以使用 dataclasses
做很多其他很酷的事情。例如,
-
使对象不可变(通过使用
@dataclasses.dataclass(frozen=True)
) -
定义 getter 和 setter
-
使用
dataclasses.field
为属性提供额外支持 -
将对象转换为字典(
.asdict()
)或元组(.astuple()
)以进行序列化和兼容性处理。
你可以在 这里 阅读更多关于 dataclasses
的内容。
2. 使用风格在 Python 中进行比较
减少和排序是任何机器学习项目中一个重要的部分。你可能会在简单数据类型的列表(例如 str
、float
等)上使用 min
、max
或 sorted
函数。但你是否知道,有一个巧妙的技巧可以扩展这些基本函数所能解决的问题的范围?
你可以使用 min
、max
和 sorted
创造性地解决问题,通过一个名为 key
的特殊参数。这个键允许你定义逻辑,从可迭代对象中的每个项目中提取一个“比较键”。
比如你想要排序以下的学生身高列表,
students = [("Jack", 168), ("Zhou", 172), ("Emma", 165), ("Shan", 170)]
sorted_heights = sorted(students, key=lambda x: x[1])
更酷的是,假设你有一个 dataclasses.dataclass
而不是这个。
sorted_heights = sorted(students, key=lambda x: x.height)
看起来很酷。也许你想找出身高最高的学生。
tallest_student = max(students, key=lambda x: x.height).name
你知道吗,你甚至可以用纯 Python 模拟 argmax
操作?对于那些不知道的人,argmax
给出列表/数组中最大值的索引。这在很多算法中都是一种强制计算。
a = [3, 4, 5, 2, 1]
max_idx = max(enumerate(a), key=lambda x: x[1])[0]
我曾多次遇到过这样的问题:写了很多行代码,而这些代码本来可以通过更关注关键点来完成。
3. 让 defaultdict 成为你的默认选择
使用字典时,有一个方便的变体可能会让你的生活更轻松。假设你要管理 3 年内股票价格的变化。假设原始格式如下。
stock_prices = [
("abc", 95), ("foo", 20), ("abc", 100),
("abc", 110), ("foo", 18), ("foo", 25)
]
你还想把这个转换为字典。你可以这样做:
stock_price_dict = {}
for code, price in stock_prices:
if code not in stock_price_dict:
stock_price_dict[code] = [price]
else:
stock_price_dict[code].append(price)
这段代码确实完成了任务。但这里有一个使用 defaultdict
的更优雅版本。
from collections import defaultdict
stock_price_dict = defaultdict(list)
for code, price in stock_prices:
stock_price_dict[code].append(price)
哇,代码这样会更简洁。无需再担心值是否已被实例化。这真的显示了你对 Python 和数据结构的了解。
4. 对 itertools 说“我愿意”
itertools
是一个内置的 Python 库,用于轻松地对数据结构进行高级迭代。
你可能在生活中遇到过需要迭代多个列表以创建一个单一列表的情况。在 Python 中你可能会这样做:
student_list = [["Jack", "Mary"], ["Zhou", "Shan"], ["Emma", "Deepti"]]
all_students = []
for students in student_list:
all_students.extend(students)
你相信吗,用 itertools
这只需一行代码?
import itertools
all_students = list(itertools.chain.from_iterables(student_list))
比如你想要移除身高低于 170cm 的学生。使用 itertools
这也是一行代码。
students = [
Student(name="Jack", height=168),
Student(name="Zhou", height=172),
Student(name="Emma", height=165),
Student(name="Shan", height=170)
]
above_170_students = list(itertools.dropwhile(lambda s: s.height<170, students))
还有许多其他有用的函数,比如 accumulate
、islice
、starmap
等。你可以在这里查看更多。使用 itertools
,而不是重新发明轮子,这样可以避免冗长和低效的代码。通过使用 itertools
,你还能享受到速度上的优势,因为它在底层有高效的 CPython 实现。
5. 打包/解包参数
打包和解包是通过星号 (*
) 和双星号 (**
) 操作符实现的。理解这一概念最简单的方法是使用函数。你可以定义一个带有打包参数或解包参数的函数。假设我们定义以下两个函数:
# f1 unpacks arguments
def f1(a: str, b: str, c: str):
return " ".join([a,b,c])
# f1 packs all arguments to args
def f2(*args):
return " ".join(args)
在调用 f1
时你只能传递 3 个参数,而 f2
可以接受任意数量的参数,这些参数被打包成一个元组 args
。所以你会这样调用:
f1("I", "love", "Python")
f2("I", "love", "Python")
f2("I", "love", "Python", "and", "argument", "packing")
这些都可以工作。如果你想要一个字典,其中键是参数,值是传递的参数,你可以使用双星号操作符。
def f3(**kwargs):
return " ".join([f"{k}={v}" for k,v in kwargs.items()])
要看看这有多好,这里是写 f2
的替代方案:
def f2(text_list: list[str]):
return " ".join(text_list)
f2(("I", "love", "Python", "..."))
那双重括号已经让我不寒而栗了!使用 *args
甜美得多。
zip()
是一个实际的函数,它接受任意数量的可迭代对象。它通过取出每个可迭代对象的第一个项、第二个项,以此类推,创建几个新的列表。对于以下示例,zip()
让你可以在两种格式之间互换;
[("a1", "b1"), ("a2", "b2"), ("a3", "b3")] # Format 1
<->
[("a1", "a2", "a3"), ("b1", "b2", "b3")] # Format 2
当你处理数据时,这是一种意外常见的需求。记得我们上面的学生示例吗?假设我们只需要排序后的学生姓名列表。我们可以简单地将元组 zip 成两个列表。
students = [("Jack", 168), ("Zhou", 172), ("Emma": 165), ("Shan", 170)]
sorted_students, _ = zip(*sorted(students, key=lambda x: x[1]))
这就是将数据从格式 1 布局到格式 2,并丢弃第二个列表(因为它将包含所有身高数据)。
如果你正在开发一个需要处理任意数量参数的函数,可以使用参数打包。这比传递一个元组或字典要优雅得多。
结论
这些是你下次在 Python 中编写 ML 模型时可以做的 5 件不同的事。坚持良好的软件工程原则和语言标准为一组工程师和科学家提供了共同的基础,从而使他们能够协同工作并快速迭代。通过选择成为更好的软件工程师,你也将为你的同事照亮前进的道路,这将实现个人和团队之间无摩擦的合作。
如果你喜欢这个故事,欢迎订阅 Medium,你将获得来自我的新内容通知,并且解锁对其他作者成千上万精彩故事的完全访问。
## 使用我的推荐链接加入 Medium - Thushan Ganegedara