【问题标题】:Working with huge text files (or quasi-csv) with Python - matrices creation from text data使用 Python 处理巨大的文本文件(或准 csv) - 从文本数据创建矩阵
【发布时间】:2020-09-30 12:23:23
【问题描述】:

我对如何解决我的问题没有正确的想法。我正在处理文本文件(*.inp - abaqus 作业文件),我想从中提取一些基本信息。到目前为止,我发现了两个主要问题:

  1. 此类文件非常大,即 500 000 行。
  2. 它们的结构并不总是类似于 csv 的

广告 1。由于数据量很大,我想包含 pandas 库以加快操作(将在优化循环中重复)

广告 2。具有“奇怪”结构的示例 *.inp 文件(请注意,“节点”和“元素”是代码中使用的实际名称,每个元素由多个节点组成,如 cube=element,每个立方体顶点=节点]:

*NODE
     1,  0.0, 0.0, 3.0
     2,  -17.0, 5.5, 2.3
     3,  51.0, 0.0, 639.8          
     5,  0.0, 5.5 , 31.0 
...
     145000, 31.3, 21.5, 99.8
*ELEMENT, ELSET=Name1, TYPE=Type1
     1527450, 265156, 273237, 265019, 265021, 275728, 273221, 265599,
     265146, 273583, 265020
     1527449, 269279, 272869, 269277, 269479, 273130, 272862, 269278,
     269489, 275729, 269627
     1527448, 272250, 272858, 275350, 273327, 272851, 275730, 275731,
     273346, 275732, 275733
...
     1126546, 265180, 275352, 273263, 273237, 275736, 275737, 275738,
     275739, 275740, 273246
*ELEMENT, ELSET=Name2, Type2
...
*SURFACE, NAME=Surf1
     12345, S5
     34567, S3
...
*STEP
*STATIC
1.0,,,1.0
*BOUNDARY
bc_1,1,3,0.0
bc_2,6,6,0.0
...
...

“*NODE”关键字下列出的值具有以下顺序: node_id, coord_x, coord_y, coord_z

这是模型中最大的数据集,这就是我想使用 pandas 的原因(像 csv 一样读取它)。对于这部分,我没有发现重大问题。

*ELEMENT" 关键字下列出的值有点复杂:

第 n 行:elementn_id、node1_id、node2_id、node3_id、node4_id、node5_id、node6_id、node7_id

第 n+1 行:node8_id、node9_id、node10_id

在这种情况下,pandas 将这部分代码作为两行(显然)导入,在 n+1 行的最后 7 列中包含 N/A 项。我使用 pd.read_csv 。请注意,从 1 到 10 的节点 id 一起形成一个元素(id 指定为第 n 行中的第一个东西)。

现在我说明问题:):

  1. 如何正确导入位于 *ELEMENT, ELSET=name1 和 *ELEMENT, ELSET=name2 之间的数据,我的目标是让矩阵中的每个元素仅使用 1 行,总共 11 列(第一个 - element_id , 2-11 - nodex_id)。
  2. 到目前为止,我将此 *.inp 文件划分为单独的文件以便能够对它们进行处理...现在我想在一个脚本中完成所有操作,即创建矩阵 A = [(node_id, coord_x, coord_y, coord_z ),...] 和矩阵 B = [(element_id, node1_id, node2_id, ... , node10_id),...] 一次。如果在这种情况下简单的 pd.read_csv 不能正常执行,该怎么做?有许多严格的字符串行不应该被导入或排除以加快脚本速度。

我的想法是将 *.inp 文件作为“打开”函数导入 python,然后添加某种标签/触发器以匹配应进一步使用的代码行(并且可能使用 pandas 处理),但在这种情况下我不使用 pandas 作为导入选项...

我相信我的问题对你们中的大多数人来说相当枯燥,但我并不是严格意义上的开发人员 :) 我不希望获得直接、现成的解决方案,而是希望获得您关于在哪里寻找潜在答案或工具的建议。

提前谢谢大家,祝你们有美好的一天, 楼主

【问题讨论】:

  • 您需要换个方向思考,主要是因为元素中的节点数取决于元素类型(例如元素类型 S4 有 4 个节点),而且您需要熟悉约定用于生成输入文件。查看旧文档中Getting started with Keywords edition 中的第 2.2 节。
  • 你好 Anbu,确实,每个元素使用不同数量的节点,从 1 到 20 不等。在这个例子中,我使用了 C3D10 元素,因为它们涵盖了我使用的所有模型的 90%。尽管如此,还是感谢您的反馈!

标签: python pandas csv text abaqus


【解决方案1】:

有趣的挑战!

只要您需要处理的文件大致遵循该结构,类似的方法可能适合您。输出见下文。

  • 文件数据内联在 io.StringIO() 中以使其自给自足,但它也可以是 open("data.inp") 文件流。
  • 如果您不是很精通 Python,那么带有 yield 魔法的生成器函数可能看起来有点神秘,对此感到抱歉。 :)
  • 确实需要“重构”内存中的 CSV 文件以供 Pandas 读取,如果您的内存不足,可能会成为瓶颈,但您可以通过提供它来了解一枪...
  • 注意“Surf1”组是如何被故意跳过的,因此您知道如何忽略某些部分。
import io
import itertools
import pandas as pd


def split_abq(inp_file):
    """
    Split an ABQ file into tuples of "group" header and lines in that group.
    """

    current_header = None
    for line in inp_file:
        line = line.strip()  # remove whitespace
        if not line:  # skip empty lines
            continue
        if line.startswith("*"):  # note headers
            current_header = line
            continue
        yield (current_header, line)  # generate lines


def group_split_abq(split_generator):
    """
    "De-repeat" the output of `split_abq` into a generator (group name, generator-of-lines)
    """
    for group, entries in itertools.groupby(split_abq(input_file), lambda pair: pair[0]):
        line_generator = (line for group, line in entries)  # A generator expression; this is evaluated lazily
        yield (group, line_generator)


def lines_to_df(line_generator, **read_csv_kwargs):
    """
    Convert an iterable of CSV-ish lines into a Pandas dataframe
    """

    # "Write" an in-memory file for Pandas to parse
    csv_io = io.StringIO()
    for line in line_generator:
        print(line, file=csv_io)
    csv_io.seek(0)  # Seek back to the start
    return pd.read_csv(csv_io, **read_csv_kwargs)


input_file = io.StringIO(
    """
*NODE
     1,  0.0, 0.0, 3.0
     2,  -17.0, 5.5, 2.3
     3,  51.0, 0.0, 639.8
     5,  0.0, 5.5 , 31.0 
     145000, 31.3, 21.5, 99.8
*ELEMENT, ELSET=Name1, TYPE=Type1
     1527450, 265156, 273237, 265019, 265021, 275728, 273221, 265599, 265146, 273583, 265020
     1527449, 269279, 272869, 269277, 269479, 273130, 272862, 269278, 269489, 275729, 269627
     1527448, 272250, 272858, 275350, 273327, 272851, 275730, 275731, 273346, 275732, 275733
     1126546, 265180, 275352, 273263, 273237, 275736, 275737, 275738, 275739, 275740, 273246
*ELEMENT, ELSET=Name2, Type2
    1527450, 265156, 273237, 265019, 265021, 275728, 273221, 265599, 265146, 273583, 265020
*SURFACE, NAME=Surf1
     12345, S5
     34567, S3
*STEP
*STATIC
1.0,,,1.0
*BOUNDARY
bc_1,1,3,0.0
bc_2,6,6,0.0
"""
)


for group, lines in group_split_abq(split_abq(input_file)):
    print("=================================")
    print("Group: ", group)
    if "Surf1" in group:  # You can use this opportunity to ignore some groups
        print("-> Skipping Surf1")
        continue
    df = lines_to_df(lines, header=None)  # `header` should probably be decided by the group type
    print(df.head())
    print("------------------------------\n")
    # You could store the various `df`s generated in a dict here

输出是

=================================
Group:  *NODE
        0     1     2      3
0       1   0.0   0.0    3.0
1       2 -17.0   5.5    2.3
2       3  51.0   0.0  639.8
3       5   0.0   5.5   31.0
4  145000  31.3  21.5   99.8
------------------------------

=================================
Group:  *ELEMENT, ELSET=Name1, TYPE=Type1
        0       1       2       3       4       5       6       7       8       9       10
0  1527450  265156  273237  265019  265021  275728  273221  265599  265146  273583  265020
1  1527449  269279  272869  269277  269479  273130  272862  269278  269489  275729  269627
2  1527448  272250  272858  275350  273327  272851  275730  275731  273346  275732  275733
3  1126546  265180  275352  273263  273237  275736  275737  275738  275739  275740  273246
------------------------------

=================================
Group:  *ELEMENT, ELSET=Name2, Type2
        0       1       2       3       4       5       6       7       8       9       10
0  1527450  265156  273237  265019  265021  275728  273221  265599  265146  273583  265020
------------------------------

=================================
Group:  *SURFACE, NAME=Surf1
-> Skipping Surf1
=================================
Group:  *STATIC
     0   1   2    3
0  1.0 NaN NaN  1.0
------------------------------

=================================
Group:  *BOUNDARY
      0  1  2    3
0  bc_1  1  3  0.0
1  bc_2  6  6  0.0
------------------------------

【讨论】:

  • 哇。这真是令人印象深刻。非常感谢 AKX 的详细而全面的回答,据我所知,它实际上完全解决了这个问题。尽管如此,我仍然需要让我的大脑有点出汗才能通过代码并理解它:) 我非常感谢您的回答,一旦我在实际示例中验证它,我会尽快回复它。 BR!
【解决方案2】:

你可能想看看使用这个解析器AbqParse,我没有尝试过,所以我不知道它是否适合你,代码也很旧,所以它可能无法工作更新版本的 python。

【讨论】:

  • 谢谢 Silas,我会在不久的将来审查 AbqParse。非常感谢您发布它,直到现在我才知道此功能。
猜你喜欢
  • 2012-05-09
  • 1970-01-01
  • 1970-01-01
  • 2017-03-20
  • 1970-01-01
  • 2018-08-03
  • 2010-11-13
  • 1970-01-01
  • 2016-01-30
相关资源
最近更新 更多