Alter Woocommerce product archive structure

I think the subcategory thing get generated with this function woocommerce_product_subcategories();. But as you said for this layout there is no specific template file. Also, if I remember right, you cannot do this with css alone.

A little while ago I created a little plugin for a customer who also wanted a product overview structured by the product categories like you want to have.

The plugin-way is one way.

The good thing with a plugin solution is that you can show your category/products list everywhere where you can add shortcodes. (post, page, widgets)
And you can also add arguments if you just want to show a single category with products or dont want to show count or description.
I also made my list collapsable, so that a customer can collapse single categories only and can hide others.

The other way, as you already mentioned, is editing the archiv-product.php way.

To do this, first copy the archiv-product.php from the woocommerce plugin directory into your theme directory inside a new folder called woocommerce.
After this, you can edit the archiv-product.php file inside your theme directory.

With both ways you than need some logic like this:
(tried my best to comment the code as we go along)

// first we need to get the product category terms ....

$categories_args = array(
    'taxonomy' => 'product_cat' // the taxonomy we want to get terms of
    //'parent' => 0, // only show terms if parent is 0 = top level terms, try it
);

$product_categories = get_terms( $categories_args ); // get all terms of the product category taxonomy

if ($product_categories) { // only start if there are some terms

    echo '<ul class="catalog">';

    // now we are looping over each term
    foreach ($product_categories as $product_category) {

        $term_id    = $product_category->term_id; // single term ID
        $term_name  = $product_category->name; // single term name
        $term_desc  = $product_category->description; // single term description
        $term_link  = get_term_link($product_category->slug, $product_category->taxonomy); // single term link


        echo '<li class="product-cat-'.$term_id.'">'; // for each term we will create one list element

        echo '<h4 class="product-cat-title"><a href="'.$term_link.'">'.$term_name.'</a></h4>'; // display term name with link

        echo '<p class="product-cat-description">'.$term_desc .'</p>'; // display term description


        // ... now we will get the products which have that term assigned...


        $products_args = array(
            'post_type'     => 'product', // we want to get products
            'tax_query'     => array(
                array(
                    'taxonomy' => 'product_cat', // the product taxonomy
                    'field'    => 'term_id', // we want to use the term_id not slug
                    'terms'    => $term_id, // here we enter the ID of the current term *this is where the magic happens*
                ),
            ),
        );

        $products = new WP_Query( $products_args );

        if ( $products->have_posts() ) { // only start if we hace some products

            // START some normal woocommerce loop, as you already posted in your question

            woocommerce_product_loop_start(); // this will generate the start of the default products list UL

            while ( $products->have_posts() ) : $products->the_post();

                wc_get_template_part( 'content', 'product' ); // we are using the default product template from woocommerce

            endwhile; // end of the loop.

            woocommerce_product_loop_end(); // this generates the end of the default woocommerce product list UL

            // END the normal woocommerce loop

            // Restore original post data, maybe not needed here (in a plugin it might be necessary)
            wp_reset_postdata();

        } else { // if we have no products, show the default woocommerce no-product loop

            // no posts found
            wc_get_template( 'loop/no-products-found.php' );

        }//END if $products

        echo '</li>';//END here is the end of our product-cat-term_id list item

    }//END foreach $product_categories

    echo '</ul>';//END of catalog list

}//END if $product_categories

The thing is you cant just replace the normal woo loop (code you posted) with this code snippet above.
You will also need to delete some other lines in the archiv-product.php file!

I created a gist of a fully working archiv-product.php file here.
I think this will be helpful to you.

Also look at get_terms() function here, and WP-Query arguments here. I just used the basic arguments that we need, surely you will need some more arguments.