pandas 在超过20,000个选定列的超大型字符串中,根据每个原始元素的前3个字符替换元素的最有效方法

sgtfey8w  于 2024-01-04  发布在  其他
关注(0)|答案(5)|浏览(159)

我试图根据一个大的数组中特定列的所有原始元素的前3个字符来替换数组中的整个元素(在适用的情况下,基于字典)。这个过程需要重复多次。考虑到有超过20,000个选定的列,下面的for循环非常慢。
请注意,其中的所有元素都是字符串。

  1. d = {'0/0': 0, '0/1': 1, '1/0': 1, '1/1': 2, "./.": 3}
  2. cols = list(set(merged.columns) - set(["subject", "Group"]))
  3. for col in cols:
  4. merged[col] = merged[col].str[:3].replace(d)

字符串
我尝试使用lamda函数(请参阅下文),然而,这也很慢。我相信是apply函数减慢了速度。(注意:使用Applymap也很慢)

  1. d = {'0/0': 0, '0/1': 1, '1/0': 1, '1/1': 2, "./.": 3}
  2. cols = list(set(merged.columns) - set(["subject", "Group"]))
  3. merged[cols] = merged[cols].apply(lambda x: x.str[:3].replace(d))


我正在寻求更有效的方法,例如使用矢量化,但未能确定前进的方向。
下面可以看到数据的示例(请注意,它比实际数据小得多,每个单元格中的字符串也长得多)

  1. data = {
  2. 'Sample1': ['0/0:0,1:33', '0/1:2,3:32', '1/0:4,5', '1/1:6,7', './.:8,9'],
  3. 'Sample2': ['0/0:10,11', '0/1:12,13', '1/0:14,15', '1/1:16,17', './.:18,19'],
  4. 'Sample3': ['0/0:20,21', '0/1:22,23', '1/0:24,25:23', '1/1:26,27', './.:28,29'],
  5. }
  6. df = pd.DataFrame(data)


更新:具有代表性大小的数据样本

  1. import numpy as np
  2. import pandas as pd
  3. sample = ['0/0:0,1:33', '0/1:2,3:32', '1/0:4,5', '1/1:6,7', './.:8,9', '0/0:10,11',
  4. '0/1:12,13', '1/0:14,15', '1/1:16,17', './.:18,19', '0/4:20,21',
  5. '0/1:22,23', '1/0:24,25:23', '1/1:26,27', './.:28,29']
  6. df = pd.DataFrame(np.random.choice(sample, (2000, 20000)))

jpfvwuh4

jpfvwuh41#

示例

让我们做2000 X 20000样品

  1. import numpy as np
  2. import pandas as pd
  3. sample = ['0/0:0,1:33', '0/1:2,3:32', '1/0:4,5', '1/1:6,7', './.:8,9', '0/0:10,11',
  4. '0/1:12,13', '1/0:14,15', '1/1:16,17', './.:18,19', '0/0:20,21',
  5. '0/1:22,23', '1/0:24,25:23', '1/1:26,27', './.:28,29']
  6. df = pd.DataFrame(np.random.choice(sample, (2000, 20000)))

字符串


的数据

验证码

生成Map程序

  1. m = {'0/0': 0, '0/1': 1, '1/0': 1, '1/1': 2, "./.": 3}


我认为使用for循环和使用apply之间没有太大的区别,因为它们都是循环。
但是,mapreplace快一点(注意,未Map的结果返回NaN),并且apply的axis=1更有效,因为列比行多。

  1. replace(你的代码)
  1. %timeit df.apply(lambda x: x.str[:3].replace(m))


结果:

  1. 1min 9s ± 5.81 s per loop (mean ± std. dev. of 7 runs, 1 loop each)


1.轴为0的贴图(默认)

  1. %timeit df.apply(lambda x: x.str[:3].map(m))


结果:

  1. 52.9 s ± 1.3 s per loop (mean ± std. dev. of 7 runs, 1 loop each)


1.轴=1的Map

  1. %timeit df.apply(lambda x: x.str[:3].map(m), axis=1)


结果:

  1. 18.3 s ± 1.54 s per loop (mean ± std. dev. of 7 runs, 1 loop each)


有可能获得稍微更有效的结果

展开查看全部
c86crjj0

c86crjj02#

这个怎么样,使用numpy<U3类型快速提取前三个字符?在一个2000 x 20_000帧上需要6. 22秒(AWS r6id.2xlarge)。

  1. def replace_match(df, d, n=3, default=-1):
  2. codes, uniques = pd.factorize(df.to_numpy().ravel().astype(f'<U{n}'))
  3. new = np.array([d.get(e, default) for e in uniques])
  4. return pd.DataFrame(
  5. new[codes].reshape(df.shape),
  6. index=df.index, columns=df.columns)

字符串
测试,一些单元格故意不匹配(输出将是default):

  1. sample = ['0/0:0,1:33', '0/4:2,3:32', '1/0:4,5', '1/1:6,7', './.:8,9', '0/0:10,11',
  2. '0/1:12,13', '1/0:14,15', '1/1:16,17', './.:18,19', '0/0:20,21',
  3. '0/1:22,23', '1/0:24,25:23', '1/1:26,27', './.:28,29']
  4. df = pd.DataFrame(np.random.choice(sample, (2000, 20_000)))
  5. d = {'0/0': 0, '0/1': 1, '1/0': 1, '1/1': 2, './.': 3}
  6. %timeit replace_match(df, d)
  7. 6.22 s ± 46.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


相比之下:
PaulS的解决方案:

  1. default = -1
  2. %timeit df.applymap(lambda x: d.get(x[:3], default))
  3. 14 s ± 44.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


(One Panda Kim的解决方案:

  1. %timeit df.apply(lambda x: x.str[:3].map(d), axis=1)
  2. 8.06 s ± 58.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


.注意,这填充了NaN,其中d中没有匹配项,因此使帧dtype=float64

展开查看全部
n1bvdmb6

n1bvdmb63#

解决方案1(最快)

到目前为止找到的最快的解决方案(它只需要3.27s(2000, 20000)维的嵌套框架),它基于获取每个嵌套框架元素(S3 dtype)的前3个字节,然后使用嵌套的np.where和比较字节的想法:

  1. a = df.astype('S3').to_numpy()
  2. pd.DataFrame(
  3. np.where(a == b'0/1', 1,
  4. np.where(a == b'0/0', 0,
  5. np.where(a == b'1/1', 2,
  6. np.where(a == b'1/0', 1, 3)))))

字符串
性能:

  • 这个新的解决方案:

3.27 s ± 30.2 ms/循环(7次运行的平均值±标准差,每次1个循环)

解决方案2

另一种可能的解决方案,基于numpy.vectorize

  1. pd.DataFrame(np.vectorize(lambda x: d[x[:3]])(df.values))


性能:
更快的已知解决方案之前,我的解决方案是@PierreD的一个.与此相比,我的解决方案(在我的机器上,(2000, 20000)尺寸的矩阵):

  • 我的:

7.31 s ± 70.8 ms/循环(7次运行的平均值±标准差,每次1个循环)

  • @PierreD的解决方案:

9.55 s ± 66.9 ms/循环(7次运行的平均值±标准差,每次1个循环)

解决方案3

或者:

  1. df.map(lambda x: d[x[:3]])


输出量:

  1. Sample1 Sample2 Sample3
  2. 0 0 0 0
  3. 1 1 1 1
  4. 2 1 1 1
  5. 3 2 2 2
  6. 4 3 3 3

展开查看全部
mm5n2pyu

mm5n2pyu4#

**根据示例更新:

  1. df = df.stack().str.extract(r'([^:]+)', expand=False).replace(d).unstack()
  2. print(df)
  3. Index Sample1 Sample2 Sample3
  4. 0 0 0 0
  5. 1 1 1 1
  6. 2 1 1 1
  7. 3 2 2 2
  8. 4 3 3 3

字符串

jum4pzuy

jum4pzuy5#

我将添加一个基准测试,允许在相同的调用中轻松测试所有函数(并使用相同大小的框架):

  1. from functools import partial
  2. import numpy as np
  3. import pandas as pd
  4. import timeit
  5. from statistics import mean, stdev
  6. def build_dataframe(size):
  7. sample = [
  8. "0/0:0,1:33",
  9. "0/1:2,3:32",
  10. "1/0:4,5",
  11. "1/1:6,7",
  12. "./.:8,9",
  13. "0/0:10,11",
  14. "0/1:12,13",
  15. "1/0:14,15",
  16. "1/1:16,17",
  17. "./.:18,19",
  18. "0/0:20,21",
  19. "0/1:22,23",
  20. "1/0:24,25:23",
  21. "1/1:26,27",
  22. "./.:28,29",
  23. ]
  24. return pd.DataFrame(np.random.choice(sample, (size, size)))
  25. def str_method(data):
  26. data = data.copy()
  27. d = {"0/0": 0, "0/1": 1, "1/0": 1, "1/1": 2, "./.": 3}
  28. for col in data.columns:
  29. data[col] = data[col].str[:3].replace(d)
  30. def apply_method(data):
  31. data = data.copy()
  32. d = {"0/0": 0, "0/1": 1, "1/0": 1, "1/1": 2, "./.": 3}
  33. data[data.columns] = data[data.columns].apply(lambda x: x.str[:3].replace(d))
  34. def stack_method(data):
  35. data = data.copy()
  36. d = {"0/0": 0, "0/1": 1, "1/0": 1, "1/1": 2, "./.": 3}
  37. data.stack().str.extract(r"([^:]+)", expand=False).replace(d).unstack()
  38. def replace_match(data, n=3, inplace=False):
  39. d = {"0/0": 0, "0/1": 1, "1/0": 1, "1/1": 2, "./.": 3}
  40. pre = data.to_numpy().astype(f'U{n}')
  41. return data.where(
  42. np.isin(pre, np.array(list(d)), invert=True),
  43. pd.DataFrame(pre, index=df.index, columns=df.columns).replace(d),
  44. inplace=inplace,
  45. )
  46. def map_method(data):
  47. data = data.copy()
  48. d = {"0/0": 0, "0/1": 1, "1/0": 1, "1/1": 2, "./.": 3}
  49. data.map(lambda x: d[x[:3]])
  50. turn_number = 100
  51. df = build_dataframe(1000)
  52. for one_method in [str_method, apply_method, stack_method, map_method, replace_match]:
  53. function_to_test = partial(one_method, data=df)
  54. durations = timeit.Timer("function_to_test()", globals=globals()).repeat(
  55. repeat=turn_number, number=1
  56. )
  57. print(f"{one_method.__name__}, mean {mean(durations)*10**3} ms, deviation {stdev(durations)*10**3} ms")

字符串
如果你有多核计算机,你应该看看dask。你可以这样做:

  1. import dask.dataframe as dd
  2. data = dd. # don't know how you build your datatframe
  3. ...
  4. # with one of the method.
  5. data2 = data.map(lambda x: d[x[:3]]
  6. data2.compute()


以下是我使用不同方法的结果:

  1. str_method, mean 1940.1417385198874 ms, deviation 30.65625035255771 ms
  2. apply_method, mean 2049.7742383097648 ms, deviation 66.40606504300902 ms
  3. stack_method, mean 1921.710312769137 ms, deviation 27.082265869582276 ms
  4. map_method, mean 900.7486290999805 ms, deviation 13.709531124297671 ms
  5. replace_match, mean 1385.514028930047 ms, deviation 20.934190486529495 ms


如果您使用的是Pierre D解决方案,那么将有更多的工作来调整解决方案以适应dask。

展开查看全部

相关问题