【问题标题】:Compute number of files per folder in a complex folder structure?计算复杂文件夹结构中每个文件夹的文件数?
【发布时间】:2017-11-14 19:30:07
【问题描述】:

我通过导入包含文件的文件夹结构创建了一个简单的data.tree

if (!require("pacman")) install.packages("pacman")
pacman::p_load_gh("trinker/pathr")

library(pathr)
library(data.tree)

folder_structure <- pathr::tree(path = "/Users/username/Downloads/top_level/",
 use.data.tree = T, include.files = T)

现在,我想将对象folder_structure 转换为data.frame,每个文件夹一行一行,一列指定每个文件夹包含多少个文件。我怎样才能做到这一点?

例如,我有一个非常简单的文件夹结构:

top_level_folder
    sub_folder_1
        file1.txt
    sub_folder_2
        file2.txt

回答这个问题需要创建一个如下所示的输出:

Folders             Files
top_level_folder    0
sub_folder_1        1
sub_folder_2        1

第一列可以通过调用list.dirs("/Users/username/Downloads/top_level/")简单生成,但我不知道如何生成第二列。请注意,第二列是非递归的,这意味着不计算子文件夹中的文件(即 top_level_folder 包含 0 文件,即使 top_level_folder 的子文件夹包含 2 个文件)。

如果您想查看您的解决方案是否可扩展,请下载 Rails 代码库:https://github.com/rails/rails/archive/master.zip 并在 Rails 更复杂的文件结构上试用。

【问题讨论】:

  • 我在哪里可以获得pathr 包?
  • 您能否澄清sub_folder_1 是否指的是同一个文件夹? IE。是一个包含两个文件的文件夹,还是两个文件夹——同名,这不起作用——因此应该是sub_folder_1sub_folder_2
  • @Felix - 那是个错误,应该是sub_folder_2--对不起!我已经在问题中更正了它。

标签: r path tree hierarchical-data directory


【解决方案1】:

list.dirs() 提供从起始文件夹可访问的每个子目录的向量,以便处理数据框的第一列。很方便。

# Get a vector of all the directories and subdirectories from this folder
dir <- "."
xs <- list.dirs(dir, recursive = TRUE)

list.files() 可以告诉我们每个文件夹的内容,但它包括文件和文件夹。我们只想要文件。要获得文件数,我们需要使用谓词过滤list.files() 的输出。 file.info() 可以告诉我们一个给定的文件是否是一个目录,所以我们以此来构建我们的谓词。

# Helper to check if something is folder or file
is_dir <- function(x) file.info(x)[["isdir"]]
is_file <- Negate(is_dir)

现在,我们解决如何获取单个文件夹中的文件数。对布尔值求和会返回 TRUE 案例的数量。

# Count the files in a single folder
count_files_in_one_dir <- function(dir) {
  files <- list.files(dir, full.names = TRUE)
  sum(is_file(files))
}

为方便起见,我们封装了该函数,使其适用于多个文件夹。

# Vectorized version of the above
count_files_in_dir <- function(dir) {
  vapply(dir, count_files_in_one_dir, numeric(1), USE.NAMES = FALSE)
}

现在我们可以统计文件了。

df <- tibble::data_frame(
  dir = xs,
  nfiles = count_files_in_dir(xs))

df
#> # A tibble: 688 x 2
#>                                                  dir nfiles
#>                                                <chr>  <dbl>
#>  1                                                 .     11
#>  2                                         ./.github      3
#>  3                                     ./actioncable      7
#>  4                                 ./actioncable/app      0
#>  5                          ./actioncable/app/assets      0
#>  6              ./actioncable/app/assets/javascripts      1
#>  7 ./actioncable/app/assets/javascripts/action_cable      5
#>  8                                 ./actioncable/bin      1
#>  9                                 ./actioncable/lib      1
#> 10                    ./actioncable/lib/action_cable      8
#> # ... with 678 more rows

【讨论】:

    【解决方案2】:

    您可以将dplyr 链与pathr 包中的parse_path() 函数一起使用。 tree 函数基本上只是 parse_path 的包装,因此直接使用 parse_path 更容易。例如。像这样:

    library(pathr)
    library(dplyr)
    
    fls <- dir("C:/RBuildTools/3.3", recursive = T, full.names = T) %>% 
    parse_path() %>% 
    index(4) %>% # this is where you indicate the level or "depth" 
                 # of the folder of which want subfolder file counts
    data.frame(folders = .) %>% 
    group_by(folders) %>% 
    tally() %>% 
    arrange(n)
    
    # if you want to get rid of all the files in your starting folder 
    # just add a 
    # filter(folder > 1) at the end of the dplyr chain
    

    对我来说,上面的代码会产生以下结果:

    > fls
    # A tibble: 12 × 2
            folders     n
             <fctr> <int>
    1       COPYING     1
    2    README.txt     1
    3    Rtools.txt     1
    4  unins000.dat     1
    5  unins000.exe     1
    6   VERSION.txt     1
    7           bin    56
    8    mingw_libs   200
    9      texinfo5   356
    10    gcc-4.6.3  3787
    11     mingw_32 13707
    12     mingw_64 14619
    

    【讨论】:

    • 这对我来说似乎根本不起作用。我已经更新了答案,以更具体地显示输出的外观。使用您的脚本,我没有获得示例文件夹结构所需的任何信息。我也不确定你所说的“深度”是什么意思——你从哪里开始计算深度,它朝哪个方向发展?
    • 例如,如果我在"/Users/username/Downloads/top_level/" 上调用您的函数,那么我只需得到Folder=DownloadsN=2
    • 啊,我明白了。抱歉,我的回答不清楚。从理论上讲,鉴于您的最后一个示例,您应该将index(4) 更改为index(5),因为您想计算第五个斜杠或文件夹之后的所有文件夹(这就是我的意思是深度。我意识到这不清楚,我要去重新制定它)
    • 但我也意识到您要求提供非递归文件夹计数(即不计算潜在子文件夹中的所有文件)。这有点棘手,因为Rdir() 只有recursive = T/F 选项,而不是命令应该在子文件夹中爬多深——这对于精确计数是必要的。我会看看我是否能想出一个解决这个问题的方法,但让我们先看看我们是否可以让上面的代码在你的机器上运行
    • 是的,如果我将文件夹深度分别设置为 5 和 6,我可以获得示例中 3 个文件夹中每个文件夹的文件数。但是,它是递归的。另外,如果我不知道所有文件夹的深度,如何将它用于大型复杂的文件夹结构?
    【解决方案3】:
    dir.create("top_level_folder")
    dir.create("top_level_folder/sub_folder_1")
    dir.create("top_level_folder/sub_folder_2")
    a <- "hello"
    save(a,file = "top_level_folder/sub_folder_1/file1.txt")
    save(a,file = "top_level_folder/sub_folder_2/file2.txt")
    
    path <- "top_level_folder"
    files   <- list.files(path, recursive=TRUE)
    folders <- sapply(strsplit(files,"/"),function(x){x[length(x)-1]})
    output <- setNames(as.data.frame(table(unlist(folders))),c("Folders","Files"))
    
    all_folders <- data.frame(Folders = list.dirs(path,full.names=FALSE,recursive=TRUE),stringsAsFactors=FALSE)
    all_folders$Folders[1] <- strsplit(path,",")[[1]][length(strsplit(path,",")[[1]])]
    
    output <- merge(all_folders,output,all.x = TRUE)
    output$Files[is.na(output$Files)] <- 0
    output <- output[match(all_folders$Folders,output$Folders),]
    
    #            Folders Files
    # 3 top_level_folder     0
    # 1     sub_folder_1     1
    # 2     sub_folder_2     1
    

    【讨论】:

    • 它适用于那个有限的例子,但是一旦我扩展到更复杂的文件结构,它就会失败:Error in table(folders) : all arguments must have the same length
    • 您可以下载 Rails 代码库并在上面试用:github.com/rails/rails/archive/master.zip
    • 我在我的 profram 文件完整文件夹中尝试它时遇到了同样的错误,但是当我将文件夹更改为 unlist(文件夹)时它可以工作,你可以试试新脚本吗?
    • 这似乎有效。这是非递归的递归吗?
    • 它是递归的,您可以尝试sample(files,100) 来查看计数示例
    【解决方案4】:

    您真正需要做的就是列出带有list.dirs(默认为recursive = TRUE)的目录并对其进行迭代,找到list.files(默认为recursive = FALSE)的长度目录。整理到一个不错的 data.frame,

    library(purrr)
    
    files <- .libPaths()[1] %>%    # omit for current directory or supply alternate path
        list.dirs() %>% 
        map_df(~list(path = .x, 
                     files = length(list.files(.x))))
    
    files
    #> # A tibble: 4,457 x 2
    #>                                                                           path files
    #>                                                                          <chr> <int>
    #>  1              /Library/Frameworks/R.framework/Versions/3.4/Resources/library   314
    #>  2        /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind     9
    #>  3   /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/help     5
    #>  4   /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/html     2
    #>  5   /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/Meta     6
    #>  6      /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/R     3
    #>  7      /Library/Frameworks/R.framework/Versions/3.4/Resources/library/acepack    14
    #>  8 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/acepack/help     5
    #>  9 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/acepack/html     2
    #> 10 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/acepack/libs     2
    #> # ... with 4,447 more rows
    

    如果您愿意,也可以全部在基地中,

    files <- do.call(rbind, lapply(list.dirs(.libPaths()[1]), function(path){
        data.frame(path = path, 
                   files = length(list.files(path)), 
                   stringsAsFactors = FALSE)
    }))
    
    head(files)
    #>                                                                        path files
    #> 1            /Library/Frameworks/R.framework/Versions/3.4/Resources/library   314
    #> 2      /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind     9
    #> 3 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/help     5
    #> 4 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/html     2
    #> 5 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/Meta     6
    #> 6    /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/R     3
    

    【讨论】:

      【解决方案5】:

      这是一个非常紧凑的解决方案:

      print(folder_structure, 
            files = function(node) sum(Get(node$children, 'isLeaf')), 
            filterFun = isNotLeaf,
            pruneMethod = NULL
      )
      

      这会产生这样的结果:

                                                           levelName files
      1   data.tree                                                     16
      2    ¦--data                                                       2
      3    ¦--data_gen                                                   2
      4    ¦--.git                                                       8
      5    ¦   ¦--hooks                                                  9
      6    ¦   ¦--info                                                   1
      7    ¦   ¦--logs                                                   1
      8    ¦   ¦   °--refs                                               1
      9    ¦   ¦       ¦--heads                                          4
      10   ¦   ¦       ¦--remotes                                        0
      11   ¦   ¦       ¦   °--origin                                     5
      12   ¦   ¦--objects                                                0
      13   ¦   ¦   ¦--01                                                 4
      14   ¦   ¦   ¦--02                                                 5
      ...
      

      但是请注意,这也将空文件夹计为文件。

      【讨论】:

        【解决方案6】:

        list.files 返回所有文件和目录路径。没有is.file 功能,但有dir.exists。由于我们知道所有路径都是实际的节点,因此那些不是目录的将被视为文件。

        top_level <- '~/rails-master'
        setwd(top_level)
        subitems <- data.frame(
          path = list.files(
            include.dirs = TRUE,
            recursive    = TRUE
          ),
          stringsAsFactors = FALSE
        )
        subitems$is_file <- !dir.exists(subitems$path)
        

        对于每一行,如果路径指向一个目录,那么它就是它自己的目录路径。如果路径是文件,则其父级是目录路径。然后只需按目录路径计算is_file 为真的频率。

        subitems$dir_path <- ifelse(
          subitems$is_file,
          dirname(subitems$path),
          subitems$path
        )
        file_counts <- tapply(subitems$is_file, subitems$dir_path, sum)
        result <- data.frame(
          Folders = names(file_counts),
          Files   = file_counts
        )
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-01-02
          • 1970-01-01
          • 2017-12-25
          • 1970-01-01
          • 1970-01-01
          • 2016-02-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多