标准化数据框(删除数据冗余并创建查找表)



我对应用于SQL等关系数据库的数据归一化技术有一些认识。https://en.wikipedia.org/wiki/database_normalization

我对将这些技术应用于从各种来源获得的平面文件(CSV(非常感兴趣。

是否有任何特定的软件包或技术有助于获得功能多余的列并有助于重新装修我的数据?可能找到所有相互依存的变量(也许完全相关?(,将它们剥离并将其复制为查找表。

r首选解决方案,因为这是我们团队的主要工具,但是Python也可以。

example
| Product | Class | Class 2 | Sales |
| A       | Red   | Large   | 12    |
| A       | Red   | Large   | 42    |
| B       | Blue  | Small   | 12    |

让产品始终为红色且大。B总是蓝色和小。因此,一种更好的存储数据的方法是:

Table 1            Lookup Table
| A | 12 |         | A | Red  | Large |
| A | 42 |         | B | Blue | Small |
| B | 12 |

这是一个相当广泛的问题,也邀请了自以为是的答案。

但是,我的答案包括四个部分:

  1. 读取数据文件
  2. 识别冗余列
  3. 分开数据
  4. 在需要时组合(加入,合并(

虽然另一个答案已经显示了如何使用base r进行步骤3,并且我建议在处理大量数据时出于性能原因使用其他替代方案(sqldftidyverse(。

步骤1:读取数据

在这里,我们模拟了来自字符串的读取数据,但fread()也将读取文件:

DT <- fread(
  "| Product | Class | Class_2 | Sales |
| A       | Red   | Large   | 12    |
| A       | Red   | Large   | 42    |
| B       | Blue  | Small   | 12    |
| B       | Blue  | Small   | 22    |
| C       | Blue  | Large   | 59    |
",
  sep = "|"
)[, Product:Sales]
DT
#   Product Class Class_2 Sales
#1:       A   Red   Large    12
#2:       A   Red   Large    42
#3:       B  Blue   Small    12
#4:       B  Blue   Small    22
#5:       C  Blue   Large    59 

请注意,添加了行W.R.T.OP的样本数据以示例步骤2。

步骤2:识别冗余列

归一化是一项复杂的设计任务。但是,它可以通过对唯一值的分析来指导:

DT[, lapply(.SD, uniqueN)]
#   Product Class Class_2 Sales
#1:       3     2       2     4

如果 redunancy的测量为重复值的百分比这将变为:

DT[, lapply(.SD, function(x) 100 * (1 - uniqueN(x) / .N))]
#   Product Class Class_2 Sales
#1:      40    60      60    20

ClassClass_2是重复值最高份额的列,而 Sales的冗余最低。

看列时,图片变得更清晰:

rbindlist(
  lapply(
    combn(names(DT), 2L, simplify = FALSE),
    function(cols) data.table(t(cols),
                              N = uniqueN(DT, by = cols))
  )
)[order(N, V1, V2)][, redundancy := 100 * (1 - N / nrow(DT))][]
#        V1      V2 N redundancy
#1:   Class Class_2 3         40
#2: Product   Class 3         40
#3: Product Class_2 3         40
#4:   Class   Sales 5          0
#5: Class_2   Sales 5          0
#6: Product   Sales 5          0

因此,为了减少整体恢复,可以在包含ClassClass_2的一个产品表中分配数据,以及第二个 sales Table Product CC_11 作为两个表中的钥匙列。

注意,这是一种临时方法,可用于此简单示例。对于带有许多列的大桌子可能会有所不同。

步骤3:分开数据

创建两个表如下

PT <- unique(DT[, -"Sales"])
PT
#   Product Class Class_2
#1:       A   Red   Large
#2:       B  Blue   Small
#3:       C  Blue   Large
ST <- DT[, c("Product", "Sales")]
ST
#   Product Sales
#1:       A    12
#2:       A    42
#3:       B    12
#4:       B    22
#5:       C    59

请注意,ST不能被删除,因为它可能包含单个交易。

步骤4:在需要时组合数据

现在,可以使用ST没有冗余信息,例如,用于聚合:

ST[, .(total_sales = sum(Sales)), by = Product]
#   Product total_sales
#1:       A          54
#2:       B          34
#3:       C          59

如果汇总结果应包括每个产品的基本数据,则可以使用Product作为键加入:

ST[, .(total_sales = sum(Sales)), by = Product][PT, on = "Product"]
#   Product total_sales Class Class_2
#1:       A          54   Red   Large
#2:       B          34  Blue   Small
#3:       C          59  Blue   Large

如果请求所有Blue产品的销售数字,则必须首先过滤PT,然后与ST连接,不包括在内,然后不匹配行:

PT[Class == "Blue"][ST, on = "Product", nomatch = 0]
#   Product Class Class_2 Sales
#1:       B  Blue   Small    12
#2:       B  Blue   Small    22
#3:       C  Blue   Large    59

如果要显示的Blue最高的CC_18产品可以通过:

来实现。
PT[Class == "Blue"][ST, on = "Product", nomatch = 0
                    ][, .(total_sales = sum(Sales)), by = .(Product, Class)
                      ][order(-total_sales)]
#   Product Class total_sales
#1:       C  Blue          59
#2:       B  Blue          34

这给出了R:

的所需结果
df <- read.table(header=TRUE, sep="|", text=
'| Product | Class | Class 2 | Sales |
  | A       | Red   | Large   | 12    |
  | A       | Red   | Large   | 42    |
  | B       | Blue  | Small   | 12    |')[-c(1,6)]
unique(df[1:3])
# > unique(df[1:3])
#    Product   Class   Class.2
# 1  A         Red     Large   
# 3  B         Blue    Small 
df[,c("Product", "Sales")]
# > df[,c("Product", "Sales")]
#     Product Sales
# 1  A           12
# 2  A           42
# 3  B           12

最新更新