分享

使用sklearn和pandas库对敏感数据进行匿名化

 大邓的Python 2021-02-23

原文标题:A simple way to anonymize data with Python and Pandas

原文链接 https:///r0f1/a-simple-way-to-anonymize-data-with-python-and-pandas-79g

作者:Florian Rohrer

译者:大邓

最近,我收到了一个数据集,其中包含有关客户的敏感信息,而这些敏感信息是不能公开的。 数据集位于我们的一台服务器上,我认为这是一个相当安全的位置。 我想将数据复制到我的本地驱动,以便更舒适地处理数据,同时不必担心数据脱敏信息被泄露。 所以,我写了一个改变数据的小脚本用来将敏感数据进行脱敏华操作。 我将详细介绍我所采取的所有步骤,并重点介绍一些方便的技巧。

任务

任务是准备一个数据集,以便以后可以用于机器学习目的(例如分类,回归,聚类)而不包含任何敏感信息。 最终数据集不应与原始数据集有太大差异,应反映初始数据集的分布。我将使用Jupyter笔记本作为我的环境。

首先,让我们导入所有必要的库。

import pandas as pd
import numpy as np
import scipy.stats
%matplotlib inline
import matplotlib.pyplot as plt
from sklearn_pandas import DataFrameMapper
from sklearn.preprocessing import LabelEncoder
# get rid of warnings
import warnings
warnings.filterwarnings("ignore")
# 对每个jupyter cell能得到超过1个的输出
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
from utils import best_fit_distribution,plot_result

我将假设您已熟悉此处使用的大多数库。 我只想强调三件事:

  • sklearn_pandas是一个方便的库,试图弥合两个包之间的差距。 它提供了一个DataFrameMapper类,它使得处理pandas.DataFrame更容易,它能让我们用更少的代码对数据进行重新编码。

  • 来自IPython.core.interactiveshell…我更改了一个Jupyter Notebook默认值,这样就显示了多个输出。 关于其他方便的Jupyter技巧的一篇很好的博客文章就在这里。

  • 最后,我们将把一些代码(best_fit_distribution,plot_result)放入一个名为utils.py的文件中,我们将放在Notebook旁边。

对于我们的分析,我们将使用泰坦尼克数据集的titanic_train.csv

df = pd.read_csv("titanic_train.csv")

df.shape
df.head()

删除脱敏信息列

现在已经加载了数据,我们将删除所有可识别个人身份的信息。 列[“PassengerId”,“Name”]包含此类信息。

请注意,[“PassengerId”,“Name”]对于每一行都是唯一的,因此如果我们构建机器学习模型,我们将在以后删除它们。

可以对[“Ticket”,“Cabin”]进行类似的论证,这几乎对于每一行都是唯一的。

在本文中为了方便,我们不会处理缺失值。 我们只是忽略所有包含缺失值的观察结果(如果行数据全为空,删除该行)。

df.drop(columns=["PassengerId""Name"], inplace=True
df.drop(columns=["Ticket""Cabin"], inplace=True
df.dropna(inplace=True)

df.shape
df.head()

类别型数据编码

随后,我们需要将SexEmbarked这种类别型数据编码数字。

其中Sex编码为0和1, Embarked编码为0,1,2。

LabelEncoder()是类别型数据编码为数字的常用类。

encoders = [(["Sex"], LabelEncoder()), (["Embarked"], LabelEncoder())]
mapper = DataFrameMapper(encoders, df_out=True)
new_cols = mapper.fit_transform(df.copy())
df = pd.concat([df.drop(columns=["Sex""Embarked"]), new_cols], axis="columns")

df.shape
df.head()

sklearn_pandas库的DataFrameMapper类会接收元组组成的列表,其中元组的第一个元素是列名,元组的第二个元素是转换器(transformer)。类似的转换器还有MinMaxScaler()、StandardScaler()、FunctionTransformer()。

在最后一行代码中,我们将编码后的数据与剩下的数据合并起来。注意,axis参数可以传axis=1,也可以axis='columns',但是推荐大家使用后一种方法,这样能增加代码的可读性。

#查看每个列名(字段)有几种值
df.nunique()
Survived      2
Pclass        3
Age          88
SibSp         6
Parch         7
Fare        219
Sex           2
Embarked      3
dtype: int64

离散型变量的处理

在上一面代码中我们打印了每个列名(字段)有几种值。一般这个变量的值的种类如果比较多,往往将该列(字段)定义为连续型变量;而当这个变量含有较少种类的值,往往将其定义为离散型变量。

在本文中,我们将阈值设置为20。当变量的值的种类大于20,定义为连续型变量。反之,定义为离散型变量

import pandas as pd
seires = pd.Series([1,2,1,1,12])
seires.nunique()  #结果是2

nunique()用来查看序列中有几种值
返回2,意思是series中只有两种值

categorical = [] #离散
continuous = [] #连续


#对列名进行迭代
for c in df.columns.tolist():
    col = df[c]
    nunique = col.nunique()
    if nunique < 20:
        categorical.append(c)
    else:
        continuous.append(c)

categorical

查看categorical内容,返回

['Survived', 'Pclass', 'SibSp', 'Parch', 'Sex', 'Embarked']
continuous
['Age', 'Fare']

numpy.random.choice(a, size=None, p=None)

首先我们决定每个字段(列名)作为变量,之后我们使用这个变量的概率函数,并将概率函数传入np.random.choice()来创建一个与原始数据分布类似的概率函数。在这里我们假设每个变量仅有5条记录。

for c in categorical:
    counts = df[c].value_counts()
    np.random.choice(list(counts.index), p=(counts/len(df)).values, size=5)

运行结果

    array([11011])
    array([33311])
    array([01000])
    array([00000])
    array([01001])
    array([01220])

连续型变量的处理

很幸运,连续型变量的处理方法已经在
stackoverflow thread找到,计算方法大致如下:

  • 使用预先设定的多个数字间隔来创建一个hist直方图

  • 使用一系列连续型分布函数,逐个运算匹配hist直方图。这个过程也会给分布函数生成参数

  • 直到某个分布函数使得数据与hist拟合的最好,拥有最小的残差平方和。此时,该分布函数就是数据的分布函数。

具体代码可见utils.py文件。连续型变量只有两个变量,分别是Age和Fare

best_distributions = []
for c in continuous:
    data = df[c]
    best_fit_name, best_fit_params = best_fit_distribution(data, 50)
    best_distributions.append((best_fit_name, best_fit_params))

best_distributions
[('fisk', (11.744665309421649-66.1552996995665794.73575225186589)),
 ('halfcauchy', (-5.537941926133496e-0917.86796415175786))]

Age变量是fisk分布,Fare变量是halfcauchy分布

best_distributions = [
    ('fisk', (11.744665309421649-66.1552996995665794.73575225186589)),
    ('halfcauchy', (-5.537941926133496e-0917.86796415175786))]

plot_result(df, continuous, best_distributions)
png
png

现在我们将上面所有的过程封装成一个函数generate_like_df

def generate_like_df(df, categorical_cols, continuous_cols, best_distributions, n, seed=0):
    np.random.seed(seed)
    d = {}

    for c in categorical_cols:
        counts = df[c].value_counts()
        d[c] = np.random.choice(list(counts.index), p=(counts/len(df)).values, size=n)

    for c, bd in zip(continuous_cols, best_distributions):
        dist = getattr(scipy.stats, bd[0])
        d[c] = dist.rvs(size=n, *bd[1])

    return pd.DataFrame(d, columns=categorical_cols+continuous_cols)
gendf = generate_like_df(df, categorical, continuous, best_distributions, n=100)

gendf.shape
gendf.head()

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多