很久以前在一个遥远的星系中...
一群人认为他们可以立即使用任何数据来产生杀手级分析,以至于到目前为止所做的任何分析之前,它是如此的光明一年。他们立即失败了! ð
除非您的数据来自您构建的系统,该系统位于炸弹避难所内,除了您之外,没有人可以访问您,也许您可以使用该系统输出的任何数据。除此之外,您几乎总是必须在数据上进行某种预处理,以便在您的用例中可以使用,因为原始数据通常会包含(特别是手动收集)空的值,错误的值条目类型(“四个”而不是“数量”列中的4个),重复的行,奇怪的列名和我个人喜欢的文本列中的错字。
将您的数据从其原始(有时称为“脏”状态)转换为“干净”状态,可以将其用于您想要使用的任何东西,称为“数据争吵”(是的,牛仔的东西ð)
这包括(不限于):
- 删除/替换空值。
- 删除重复。
- 重命名列。
- 创建新列。
- 放下无关的列。
- 更改列数据类型。
- 丰富来自外部来源的数据。
- ...
好消息是,如果您了解一点python并对数据争吵操作有基本的理解,那么您可以使用Python,更具体地说是Pandasð¼软件包来完成大多数争吵的事情。 ð¶©
因此,让我们开始“与Python的数据争吵”速成课程ð
以下部分假设您已经在系统上安装了Python。正常的Python安装或A conda 安装都可以。(可选)创建虚拟环境:
使用Python,您可以创建与基本安装隔离的几个环境,以使用特定的Python/软件包版本开发,测试内容,尝试新包或新软件包功能或用于组织目的。在大多数情况下,这是一件好事ð。以下将创建一个名为wranglingwithpandas的文件夹,CD进入,创建一个称为venv_pandas的虚拟环境并激活它。
在您的终端中,键入
mkdir wranglingWithPandas
cd wranglingWithPandas
pip -m venv venv_pandas
.\venv_pandas\Scripts\activate
(Windows)
source ./venv_pandas/bin/activate
(linux / macos)< / p>
安装熊猫:
pip install pandas
(可选)安装ipython或jupyter实验室
建议使用数据在交互式环境中工作时。一种方法是安装 ipyton 或 jupyterlab packages,无论您熟悉ðä·ââ€â。
(只需要一个)
pip install ipython
pip install jupyterlab
要运行它们,键入 ipython 或 jupyter lab (请注意终端中的空间)。
导入数据:
pandas可以从多种数据源导入数据。通常,它使用函数 read _ x 其中“ x”为csv,excel,json,...等。在本教程中,我们将使用csv(comma分离的值)表示杂货店中的交易(购买)日志的文件。每行代表单个交易中的一个项目。一项交易可以有多个项目。有时,手动条目可能发生ð。您可以在此Github repo中的本教程中找到示例数据集和所有代码。本教程的目标是 wrangle 该文件以找出每项类别的总收入是什么和购买的前3个项目。
要以“ dataframe”(pandas的方式,表ð)读取CSV文件,我们使用read_csv函数:
import pandas as pd # A pandas convention 😅
import re # Regex library. We will use that later.
import pathlib # To manage paths in an OOP why.
import datetime # To manage dates.
file_path = pathlib.Path("store_data_20230116.csv") # Assuming the file is in the same directory.
store_data = pd.read_csv(file_path)
数据探索:
数据探索的目的是了解手头的数据并确定数据包含的问题。快速探索它的一种方法是使用 head 和 info 方法如下。
store_data.head()
store_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10038 entries, 0 to 10037
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Tran ID 10038 non-null int64
1 Cat 9956 non-null object
2 Item 9966 non-null object
3 Qty 10023 non-null float64
4 Unit Price 10011 non-null float64
dtypes: float64(2), int64(1), object(2)
memory usage: 392.2+ KB
和探索不同的值,您可以使用 unique 方法。
store_data.Item.unique()
array(['black-eyed peas', 'chickpeas', 'apples', 'grapes', 'lettuce',
'lentils', 'potatos', 'bananas', 'carrots', 'broccoli', 'LETTUCE',
nan, 'oranges', 'navy beans', 'Oranges', 'Apples', 'Carrots',
'Potatos', 'LENTILS', 'Broccoli', 'Chickpeas', 'CARROTS',
'NAVY BEANS', 'Navy beans', 'BANANAS', 'BROCCOLI', 'Bananas',
'Lentils', 'Lettuce', 'ORANGES', 'Black-eyed peas', 'Grapes',
'GRAPES', 'CHICKPEAS', 'APPLES', 'BLACK-EYED PEAS'], dtype=object)
store_data.Cat.unique()
array(['legumes', 'fruits', 'vegetables', nan, 'VEGETABLES', 'Fruits',
'Legumes', 'Vegetables', 'FRUITS', 'LEGUMES'], dtype=object)
您可以看到, cat 和 item 列包含相同的项目(每列),但文本情况有所不同。这可能是由于手册输入所致。我们稍后会处理。
我们遇到的另一个问题是可怕的零值ð£!
isnull 和 isna 方法可以帮助识别数据中的无效值。
store_data.isnull().sum()
Tran ID 0
Cat 82
Item 72
Qty 15
Unit Price 27
dtype: int64
显然除 tran ID 包含零值外,所有列显然!
重复的行是您在数据中可能会发现的另一个问题! 重复的方法可以帮助识别这些方法。
store_data.duplicated().sum() # Duplicated records count.
175
store_data[store_data.duplicated(keep=False)].head(10)
现在,为了有趣的部分,数据争吵ð:
正如我所说的那样,数据争吵包括很多操作。您可以根据数据集中发现的数据问题执行全部或所有子集。
在本教程中,我们将进行一些基本操作来处理我们发现的内容。
我们将首先重命名列。我们可以批量或单独执行此操作。使用数据框时,我个人更喜欢较低的案例列名称,而没有任何空格。这使得引用列juuuust是一个litle位ððÖ,并避免了包含空格的列名称的许多潜在问题。这就是我们在这里做的。
# Doing a bulk rename
store_data.columns = [col.replace(" ", "_").lower() for col in store_data.columns]
# Doing individual rename
store_data.rename(
columns={
"cat": "category",
"qty": "quantity"
},
inplace=True # Without this, the rename method with return a copy of the modified dataframe.
)
store_data.columns
Index(['tran_id', 'category', 'item', 'quantity', 'unit_price'], dtype='object')
现在,我们将摆脱我们早些时候看到的175行,因为不太可能在同一交易中重复进行项目,只需增加项目的数量。此处使用的重复方法决定,当所有列都相同时,这些行是重复的。
# One way to drop the duplicates is to not select them.
count_before = len(store_data)
store_data = store_data[~store_data.duplicated()]
count_after = len(store_data)
print(f"Rows count before removing duplicates {count_before}, and after {count_after}. Thid diff is {count_before - count_after}")
Rows count before removing duplicates 10038, and after 9863. Thid diff is 175
接下来,此分析不需要 tran_id 。让我们放下它!
store_data.drop(
columns="tran_id",
inplace=True
)
store_data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 9863 entries, 0 to 10037
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 category 9781 non-null object
1 item 9791 non-null object
2 quantity 9848 non-null float64
3 unit_price 9836 non-null float64
dtypes: float64(2), object(2)
memory usage: 385.3+ KB
还记得我们发现类别和 item 列的文本相似,但情况有所不同吗?让我们纠正。
store_data.category = store_data.category.str.lower()
store_data.item = store_data.item.str.lower()
使用 str ,您可以访问Pandas和 lower()提供的许多字符串功能,是 vectorized (您可以将其视为并行工作)整个Pandas的系列(列)。
处理零值(又称丢失值)本身就是一个整体主题。关于丢失数据和分析的一个错误决定朝着完全不同的方向(也许是错误的!ð-)。我们将尝试在给定上下文中处理此数据集中的零值。
如果您回想起,我们发现所有列都包含空值( tran_id 除外,但我们已经删除了它)。
我们将要处理的第一列是数量。
我决定将所有行丢失数量掉落。毕竟,我无法从任何地方推断(准确地猜测ð)这个数字,错误地推断数量可能会影响我们的分析质量。此外,它们仅是大约10k行ð的15行。为此, dropna 方法是您的freind。
store_data.dropna(
subset="quantity",
inplace=True
)
另一种情况,我们可以以零值删除行,是类别和 item 都是nulls!这些行基本上毫无意义。 (如果您可以为每个项目假设不同且唯一的unit_price,则可以推断出这些列。但是此假设很危险)
store_data.dropna(
subset=["quantity", "item"],
inplace=True
)
如果我们现在检查具有空值的列,我们现在会发现,只有类别和 unit_price 包含它们。
store_data.isna().sum()
category 48
item 0
quantity 0
unit_price 15
dtype: int64
如果我们假设每个项目只有一个类别,则可以轻松获得类别中的缺失值列。
categories = store_data[["category", "item"]].groupby(by=["category", "item"], as_index=False).count()
categories_dict = {row["item"]: row["category"] for _, row in categories.iterrows()}
categories_dict
{'apples': 'fruits',
'bananas': 'fruits',
'grapes': 'fruits',
'oranges': 'fruits',
'black-eyed peas': 'legumes',
'chickpeas': 'legumes',
'lentils': 'legumes',
'navy beans': 'legumes',
'broccoli': 'vegetables',
'carrots': 'vegetables',
'lettuce': 'vegetables',
'potatos': 'vegetables'}
基本上,我们为项目类别创建了一个“查找字典”。现在,我们可以使用多功能应用方法来使用此dict,如下所示。
store_data.category = store_data.item.apply(lambda x: categories_dict[x])
可以使用相同的技术来获取缺失的 unit_price 值。
unit_prices = store_data[["item", "unit_price"]].groupby(by=["item", "unit_price"], as_index=False).count()
unit_prices_dict = {row["item"]: row["unit_price"] for _, row in unit_prices.iterrows()}
store_data.unit_price = store_data.item.apply(lambda x: unit_prices_dict[x])
store_data.isna().sum()
category 0
item 0
quantity 0
unit_price 0
yaaaayð!没有丢失的值!
我们还可以更改列的数据类型。我们可以为数量列做到这一点,因为我们可以将其类型更改为int,因为我们方案中的数量不可能是分数。除此之外,我们还可以丰富我们的数据。就像我们可以添加 total_amount 列计算为数量 x unit_price 和a done_on 列包含此数据集的日期是创建可以从CSV文件名获得的。
store_data.quantity = store_data.quantity.astype(int)
store_data["total_amount"] = store_data.quantity * store_data.unit_price
file_date = re.search(r"\d+", file_path.stem).group() # Regex to match all the digits in the file name without the extension (stem)
file_date = datetime.datetime.strptime(file_date, "%Y%m%d") # Parse a date object from the previouse match.
store_data["done_on"] = file_date
而现在,分析ðÖ©©
首先,每个项目类别的总收入。
(
store_data[["category", "total_amount"]]
.groupby(by="category")
.sum()
)
接下来,前三名购买的物品
(
store_data[["item", "quantity"]]
.groupby(by="item")
.sum()
.sort_values(by="quantity", ascending=False)
.head(3)
)
当然,任何分析通常都是使用图形和图进行的!但是,当您得到想法ð。
时,这些表现在就足够了。结论
在处理任何数据集时,数据争吵非常重要。而且这也很耗时!数据字段中有一个统计数据,数据科学家只花了80%的时间来准备数据ð! Python和Pandas在该域中做得很好,但并非没有局限性(Checkout Apache Spark,看看为什么它用于大数据处理而不是Pandas)!
我在本教程中构建的场景不是您可以面对的最好的现实生活。但是,嘿,它做了工作ð。
最后,您可能在这里遇到了一些看起来更像咒语的代码,而不是一块代码ð。如果您刚开始使用Python,那是正常的。但是我无法在这里解释一切!我强烈鼓励您看看随附的notebook。它包含本教程中的所有代码,并提供额外的解释。而且,要查找在线论坛或文档中不了解的任何内容,但不要让它通过!这就是您的学习方式。